From 20c51fbe9370605f03337defd1ec1d758e055751 Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 16 Oct 2025 18:38:42 -0500 Subject: [PATCH] Update source files --- .cargo/config.toml | 11 + .github/workflows/builds.yml | 177 + .github/workflows/quality.yml | 84 + .gitignore | 33 + .source-commit | 1 + Cargo.lock | 7809 +++++++++++++++++ Cargo.toml | 61 + Justfile | 92 + LICENSE | 13 + crates/borders-core/Cargo.toml | 91 + crates/borders-core/assets/maps/World.json | 76 + crates/borders-core/assets/maps/World.png | Bin 0 -> 189026 bytes crates/borders-core/build.rs | 74 + crates/borders-core/src/app.rs | 146 + crates/borders-core/src/build_info.rs | 21 + crates/borders-core/src/game/ai/bot.rs | 483 + crates/borders-core/src/game/ai/mod.rs | 7 + crates/borders-core/src/game/combat/active.rs | 264 + .../src/game/combat/calculator.rs | 145 + .../borders-core/src/game/combat/executor.rs | 377 + crates/borders-core/src/game/combat/mod.rs | 7 + crates/borders-core/src/game/core/action.rs | 47 + .../borders-core/src/game/core/constants.rs | 261 + crates/borders-core/src/game/core/instance.rs | 7 + .../borders-core/src/game/core/lifecycle.rs | 201 + crates/borders-core/src/game/core/mod.rs | 22 + crates/borders-core/src/game/core/outcome.rs | 81 + crates/borders-core/src/game/core/rng.rs | 126 + .../src/game/core/turn_execution.rs | 180 + crates/borders-core/src/game/core/utils.rs | 82 + .../src/game/entities/components.rs | 128 + .../src/game/entities/entity_map.rs | 23 + crates/borders-core/src/game/entities/mod.rs | 9 + crates/borders-core/src/game/input/context.rs | 67 + .../borders-core/src/game/input/handlers.rs | 259 + crates/borders-core/src/game/input/mod.rs | 9 + crates/borders-core/src/game/mod.rs | 25 + .../borders-core/src/game/ships/components.rs | 100 + crates/borders-core/src/game/ships/mod.rs | 14 + .../src/game/ships/ship_manager.rs | 161 + .../src/game/ships/ship_pathfinding.rs | 245 + crates/borders-core/src/game/ships/systems.rs | 267 + .../src/game/ships/transport_ship.rs | 92 + .../borders-core/src/game/systems/borders.rs | 158 + .../borders-core/src/game/systems/income.rs | 31 + crates/borders-core/src/game/systems/mod.rs | 16 + crates/borders-core/src/game/systems/spawn.rs | 78 + .../src/game/systems/spawn_timeout.rs | 75 + crates/borders-core/src/game/systems/turn.rs | 36 + .../src/game/terrain/connectivity.rs | 120 + crates/borders-core/src/game/terrain/data.rs | 250 + crates/borders-core/src/game/terrain/mod.rs | 9 + crates/borders-core/src/game/world/changes.rs | 184 + crates/borders-core/src/game/world/coastal.rs | 73 + crates/borders-core/src/game/world/manager.rs | 234 + crates/borders-core/src/game/world/mod.rs | 15 + .../borders-core/src/game/world/ownership.rs | 166 + crates/borders-core/src/game/world/tilemap.rs | 446 + crates/borders-core/src/lib.rs | 16 + crates/borders-core/src/networking/client.rs | 116 + .../src/networking/coordinator.rs | 198 + crates/borders-core/src/networking/dns.rs | 98 + crates/borders-core/src/networking/mod.rs | 146 + crates/borders-core/src/networking/network.rs | 244 + .../borders-core/src/networking/protocol.rs | 23 + .../src/networking/server/broadcast.rs | 87 + .../networking/server/connection_handler.rs | 214 + .../borders-core/src/networking/server/mod.rs | 12 + .../src/networking/server/registry.rs | 72 + crates/borders-core/src/platform.rs | 21 + crates/borders-core/src/plugin.rs | 808 ++ crates/borders-core/src/telemetry/client.rs | 315 + crates/borders-core/src/telemetry/mod.rs | 122 + .../borders-core/src/telemetry/system_info.rs | 148 + crates/borders-core/src/telemetry/types.rs | 77 + crates/borders-core/src/telemetry/user_id.rs | 268 + crates/borders-core/src/time.rs | 58 + crates/borders-core/src/ui/input.rs | 350 + crates/borders-core/src/ui/leaderboard.rs | 177 + crates/borders-core/src/ui/mod.rs | 107 + crates/borders-core/src/ui/plugin.rs | 53 + crates/borders-core/src/ui/protocol.rs | 615 ++ crates/borders-core/src/ui/transport.rs | 265 + crates/borders-core/tests/terrain.rs | 37 + crates/borders-desktop/.gitignore | 7 + crates/borders-desktop/Cargo.toml | 29 + crates/borders-desktop/build.rs | 3 + .../borders-desktop/capabilities/default.json | 17 + crates/borders-desktop/icons/128x128.png | Bin 0 -> 3512 bytes crates/borders-desktop/icons/128x128@2x.png | Bin 0 -> 7012 bytes crates/borders-desktop/icons/32x32.png | Bin 0 -> 974 bytes .../icons/Square107x107Logo.png | Bin 0 -> 2863 bytes .../icons/Square142x142Logo.png | Bin 0 -> 3858 bytes .../icons/Square150x150Logo.png | Bin 0 -> 3966 bytes .../icons/Square284x284Logo.png | Bin 0 -> 7737 bytes .../borders-desktop/icons/Square30x30Logo.png | Bin 0 -> 903 bytes .../icons/Square310x310Logo.png | Bin 0 -> 8591 bytes .../borders-desktop/icons/Square44x44Logo.png | Bin 0 -> 1299 bytes .../borders-desktop/icons/Square71x71Logo.png | Bin 0 -> 2011 bytes .../borders-desktop/icons/Square89x89Logo.png | Bin 0 -> 2468 bytes crates/borders-desktop/icons/StoreLogo.png | Bin 0 -> 1523 bytes crates/borders-desktop/icons/icon.icns | Bin 0 -> 98451 bytes crates/borders-desktop/icons/icon.ico | Bin 0 -> 86642 bytes crates/borders-desktop/icons/icon.png | Bin 0 -> 14183 bytes crates/borders-desktop/src/analytics.rs | 51 + crates/borders-desktop/src/main.rs | 107 + crates/borders-desktop/src/plugin.rs | 199 + crates/borders-desktop/src/render_bridge.rs | 222 + crates/borders-desktop/tauri.conf.json | 40 + crates/borders-server/Cargo.toml | 15 + crates/borders-server/src/main.rs | 152 + crates/borders-wasm/Cargo.toml | 33 + crates/borders-wasm/src/bridge.rs | 69 + crates/borders-wasm/src/lib.rs | 98 + crates/borders-wasm/src/render_bridge.rs | 68 + frontend/.prettierrc | 5 + frontend/index.html | 7 + frontend/package.json | 52 + frontend/pages/+Head.tsx | 45 + frontend/pages/+Wrapper.client.tsx | 70 + frontend/pages/+Wrapper.tsx | 17 + frontend/pages/+config.js | 14 + frontend/pages/fonts.css | 19 + frontend/pages/index/+Page.css | 250 + frontend/pages/index/+Page.tsx | 74 + frontend/pages/index/Game.client.tsx | 162 + frontend/pnpm-lock.yaml | 2858 ++++++ frontend/pnpm-workspace.yaml | 3 + frontend/src/assets/design/multiplayer.psd | Bin 0 -> 48700 bytes frontend/src/assets/design/settings.psd | Bin 0 -> 48884 bytes frontend/src/assets/design/singleplayer.psd | Bin 0 -> 29550 bytes .../src/assets/images/menu/multiplayer.png | Bin 0 -> 3117 bytes frontend/src/assets/images/menu/settings.png | Bin 0 -> 2520 bytes .../src/assets/images/menu/singleplayer.png | Bin 0 -> 1519 bytes frontend/src/assets/images/screenshot.png | Bin 0 -> 184950 bytes frontend/src/browser/analytics/index.ts | 5 + .../src/browser/analytics/wasmAnalytics.ts | 30 + frontend/src/browser/api/wasmBridge.ts | 362 + frontend/src/browser/workers/game.worker.ts | 84 + .../src/desktop/analytics/tauriAnalytics.ts | 32 + frontend/src/desktop/api/tauriAPI.ts | 519 ++ frontend/src/lib/utils.ts | 6 + frontend/src/shared/analytics/AnalyticsAPI.ts | 24 + .../src/shared/analytics/AnalyticsContext.tsx | 13 + frontend/src/shared/analytics/index.ts | 2 + frontend/src/shared/api/GameAPI.ts | 100 + frontend/src/shared/api/GameAPIContext.tsx | 29 + frontend/src/shared/api/index.ts | 4 + frontend/src/shared/api/types.ts | 72 + .../shared/components/game/AttackControls.tsx | 168 + .../src/shared/components/game/AttackRow.tsx | 73 + .../shared/components/game/AttacksList.tsx | 74 + .../src/shared/components/game/Canvas.tsx | 233 + .../shared/components/game/Leaderboard.tsx | 251 + frontend/src/shared/components/game/Menu.css | 39 + frontend/src/shared/components/game/Menu.tsx | 99 + .../src/shared/components/menu/MenuScreen.css | 672 ++ .../src/shared/components/menu/MenuScreen.tsx | 262 + .../components/menu/views/Downloads.tsx | 37 + .../components/menu/views/Multiplayer.tsx | 37 + .../shared/components/menu/views/Settings.tsx | 30 + .../components/overlays/AlphaWarning.tsx | 25 + .../components/overlays/BlurOverlay.tsx | 51 + .../shared/components/overlays/GameEnd.tsx | 133 + .../shared/components/overlays/SpawnPhase.tsx | 61 + frontend/src/shared/hooks/index.ts | 1 + .../src/shared/hooks/useThrottledCallback.ts | 75 + .../src/shared/render/CameraController.ts | 377 + frontend/src/shared/render/GameRenderer.ts | 519 ++ frontend/src/shared/render/ShipsRenderer.ts | 201 + .../src/shared/render/TerritoriesRenderer.ts | 436 + frontend/src/shared/render/index.ts | 2 + frontend/src/shared/styles/animations.css | 55 + frontend/src/shared/utils/binaryDecoding.ts | 82 + frontend/src/vite-env.d.ts | 17 + frontend/tsconfig.browser.json | 10 + frontend/tsconfig.desktop.json | 10 + frontend/tsconfig.json | 32 + frontend/tsconfig.node.json | 10 + frontend/vite.config.ts | 127 + rust-toolchain.toml | 3 + scripts/release-manager/.env.example | 19 + scripts/release-manager/.gitignore | 16 + scripts/release-manager/bun.lock | 741 ++ scripts/release-manager/cli.ts | 605 ++ scripts/release-manager/manifest.json | 132 + scripts/release-manager/package.json | 26 + scripts/release-manager/pnpm-workspace.yaml | 2 + scripts/release-manager/tsconfig.json | 19 + scripts/release-manager/types.ts | 54 + scripts/release-manager/utils.ts | 162 + 191 files changed, 30862 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .github/workflows/builds.yml create mode 100644 .github/workflows/quality.yml create mode 100644 .gitignore create mode 100644 .source-commit create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Justfile create mode 100644 LICENSE create mode 100644 crates/borders-core/Cargo.toml create mode 100644 crates/borders-core/assets/maps/World.json create mode 100644 crates/borders-core/assets/maps/World.png create mode 100644 crates/borders-core/build.rs create mode 100644 crates/borders-core/src/app.rs create mode 100644 crates/borders-core/src/build_info.rs create mode 100644 crates/borders-core/src/game/ai/bot.rs create mode 100644 crates/borders-core/src/game/ai/mod.rs create mode 100644 crates/borders-core/src/game/combat/active.rs create mode 100644 crates/borders-core/src/game/combat/calculator.rs create mode 100644 crates/borders-core/src/game/combat/executor.rs create mode 100644 crates/borders-core/src/game/combat/mod.rs create mode 100644 crates/borders-core/src/game/core/action.rs create mode 100644 crates/borders-core/src/game/core/constants.rs create mode 100644 crates/borders-core/src/game/core/instance.rs create mode 100644 crates/borders-core/src/game/core/lifecycle.rs create mode 100644 crates/borders-core/src/game/core/mod.rs create mode 100644 crates/borders-core/src/game/core/outcome.rs create mode 100644 crates/borders-core/src/game/core/rng.rs create mode 100644 crates/borders-core/src/game/core/turn_execution.rs create mode 100644 crates/borders-core/src/game/core/utils.rs create mode 100644 crates/borders-core/src/game/entities/components.rs create mode 100644 crates/borders-core/src/game/entities/entity_map.rs create mode 100644 crates/borders-core/src/game/entities/mod.rs create mode 100644 crates/borders-core/src/game/input/context.rs create mode 100644 crates/borders-core/src/game/input/handlers.rs create mode 100644 crates/borders-core/src/game/input/mod.rs create mode 100644 crates/borders-core/src/game/mod.rs create mode 100644 crates/borders-core/src/game/ships/components.rs create mode 100644 crates/borders-core/src/game/ships/mod.rs create mode 100644 crates/borders-core/src/game/ships/ship_manager.rs create mode 100644 crates/borders-core/src/game/ships/ship_pathfinding.rs create mode 100644 crates/borders-core/src/game/ships/systems.rs create mode 100644 crates/borders-core/src/game/ships/transport_ship.rs create mode 100644 crates/borders-core/src/game/systems/borders.rs create mode 100644 crates/borders-core/src/game/systems/income.rs create mode 100644 crates/borders-core/src/game/systems/mod.rs create mode 100644 crates/borders-core/src/game/systems/spawn.rs create mode 100644 crates/borders-core/src/game/systems/spawn_timeout.rs create mode 100644 crates/borders-core/src/game/systems/turn.rs create mode 100644 crates/borders-core/src/game/terrain/connectivity.rs create mode 100644 crates/borders-core/src/game/terrain/data.rs create mode 100644 crates/borders-core/src/game/terrain/mod.rs create mode 100644 crates/borders-core/src/game/world/changes.rs create mode 100644 crates/borders-core/src/game/world/coastal.rs create mode 100644 crates/borders-core/src/game/world/manager.rs create mode 100644 crates/borders-core/src/game/world/mod.rs create mode 100644 crates/borders-core/src/game/world/ownership.rs create mode 100644 crates/borders-core/src/game/world/tilemap.rs create mode 100644 crates/borders-core/src/lib.rs create mode 100644 crates/borders-core/src/networking/client.rs create mode 100644 crates/borders-core/src/networking/coordinator.rs create mode 100644 crates/borders-core/src/networking/dns.rs create mode 100644 crates/borders-core/src/networking/mod.rs create mode 100644 crates/borders-core/src/networking/network.rs create mode 100644 crates/borders-core/src/networking/protocol.rs create mode 100644 crates/borders-core/src/networking/server/broadcast.rs create mode 100644 crates/borders-core/src/networking/server/connection_handler.rs create mode 100644 crates/borders-core/src/networking/server/mod.rs create mode 100644 crates/borders-core/src/networking/server/registry.rs create mode 100644 crates/borders-core/src/platform.rs create mode 100644 crates/borders-core/src/plugin.rs create mode 100644 crates/borders-core/src/telemetry/client.rs create mode 100644 crates/borders-core/src/telemetry/mod.rs create mode 100644 crates/borders-core/src/telemetry/system_info.rs create mode 100644 crates/borders-core/src/telemetry/types.rs create mode 100644 crates/borders-core/src/telemetry/user_id.rs create mode 100644 crates/borders-core/src/time.rs create mode 100644 crates/borders-core/src/ui/input.rs create mode 100644 crates/borders-core/src/ui/leaderboard.rs create mode 100644 crates/borders-core/src/ui/mod.rs create mode 100644 crates/borders-core/src/ui/plugin.rs create mode 100644 crates/borders-core/src/ui/protocol.rs create mode 100644 crates/borders-core/src/ui/transport.rs create mode 100644 crates/borders-core/tests/terrain.rs create mode 100644 crates/borders-desktop/.gitignore create mode 100644 crates/borders-desktop/Cargo.toml create mode 100644 crates/borders-desktop/build.rs create mode 100644 crates/borders-desktop/capabilities/default.json create mode 100644 crates/borders-desktop/icons/128x128.png create mode 100644 crates/borders-desktop/icons/128x128@2x.png create mode 100644 crates/borders-desktop/icons/32x32.png create mode 100644 crates/borders-desktop/icons/Square107x107Logo.png create mode 100644 crates/borders-desktop/icons/Square142x142Logo.png create mode 100644 crates/borders-desktop/icons/Square150x150Logo.png create mode 100644 crates/borders-desktop/icons/Square284x284Logo.png create mode 100644 crates/borders-desktop/icons/Square30x30Logo.png create mode 100644 crates/borders-desktop/icons/Square310x310Logo.png create mode 100644 crates/borders-desktop/icons/Square44x44Logo.png create mode 100644 crates/borders-desktop/icons/Square71x71Logo.png create mode 100644 crates/borders-desktop/icons/Square89x89Logo.png create mode 100644 crates/borders-desktop/icons/StoreLogo.png create mode 100644 crates/borders-desktop/icons/icon.icns create mode 100644 crates/borders-desktop/icons/icon.ico create mode 100644 crates/borders-desktop/icons/icon.png create mode 100644 crates/borders-desktop/src/analytics.rs create mode 100644 crates/borders-desktop/src/main.rs create mode 100644 crates/borders-desktop/src/plugin.rs create mode 100644 crates/borders-desktop/src/render_bridge.rs create mode 100644 crates/borders-desktop/tauri.conf.json create mode 100644 crates/borders-server/Cargo.toml create mode 100644 crates/borders-server/src/main.rs create mode 100644 crates/borders-wasm/Cargo.toml create mode 100644 crates/borders-wasm/src/bridge.rs create mode 100644 crates/borders-wasm/src/lib.rs create mode 100644 crates/borders-wasm/src/render_bridge.rs create mode 100644 frontend/.prettierrc create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/pages/+Head.tsx create mode 100644 frontend/pages/+Wrapper.client.tsx create mode 100644 frontend/pages/+Wrapper.tsx create mode 100644 frontend/pages/+config.js create mode 100644 frontend/pages/fonts.css create mode 100644 frontend/pages/index/+Page.css create mode 100644 frontend/pages/index/+Page.tsx create mode 100644 frontend/pages/index/Game.client.tsx create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/pnpm-workspace.yaml create mode 100644 frontend/src/assets/design/multiplayer.psd create mode 100644 frontend/src/assets/design/settings.psd create mode 100644 frontend/src/assets/design/singleplayer.psd create mode 100644 frontend/src/assets/images/menu/multiplayer.png create mode 100644 frontend/src/assets/images/menu/settings.png create mode 100644 frontend/src/assets/images/menu/singleplayer.png create mode 100644 frontend/src/assets/images/screenshot.png create mode 100644 frontend/src/browser/analytics/index.ts create mode 100644 frontend/src/browser/analytics/wasmAnalytics.ts create mode 100644 frontend/src/browser/api/wasmBridge.ts create mode 100644 frontend/src/browser/workers/game.worker.ts create mode 100644 frontend/src/desktop/analytics/tauriAnalytics.ts create mode 100644 frontend/src/desktop/api/tauriAPI.ts create mode 100644 frontend/src/lib/utils.ts create mode 100644 frontend/src/shared/analytics/AnalyticsAPI.ts create mode 100644 frontend/src/shared/analytics/AnalyticsContext.tsx create mode 100644 frontend/src/shared/analytics/index.ts create mode 100644 frontend/src/shared/api/GameAPI.ts create mode 100644 frontend/src/shared/api/GameAPIContext.tsx create mode 100644 frontend/src/shared/api/index.ts create mode 100644 frontend/src/shared/api/types.ts create mode 100644 frontend/src/shared/components/game/AttackControls.tsx create mode 100644 frontend/src/shared/components/game/AttackRow.tsx create mode 100644 frontend/src/shared/components/game/AttacksList.tsx create mode 100644 frontend/src/shared/components/game/Canvas.tsx create mode 100644 frontend/src/shared/components/game/Leaderboard.tsx create mode 100644 frontend/src/shared/components/game/Menu.css create mode 100644 frontend/src/shared/components/game/Menu.tsx create mode 100644 frontend/src/shared/components/menu/MenuScreen.css create mode 100644 frontend/src/shared/components/menu/MenuScreen.tsx create mode 100644 frontend/src/shared/components/menu/views/Downloads.tsx create mode 100644 frontend/src/shared/components/menu/views/Multiplayer.tsx create mode 100644 frontend/src/shared/components/menu/views/Settings.tsx create mode 100644 frontend/src/shared/components/overlays/AlphaWarning.tsx create mode 100644 frontend/src/shared/components/overlays/BlurOverlay.tsx create mode 100644 frontend/src/shared/components/overlays/GameEnd.tsx create mode 100644 frontend/src/shared/components/overlays/SpawnPhase.tsx create mode 100644 frontend/src/shared/hooks/index.ts create mode 100644 frontend/src/shared/hooks/useThrottledCallback.ts create mode 100644 frontend/src/shared/render/CameraController.ts create mode 100644 frontend/src/shared/render/GameRenderer.ts create mode 100644 frontend/src/shared/render/ShipsRenderer.ts create mode 100644 frontend/src/shared/render/TerritoriesRenderer.ts create mode 100644 frontend/src/shared/render/index.ts create mode 100644 frontend/src/shared/styles/animations.css create mode 100644 frontend/src/shared/utils/binaryDecoding.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.browser.json create mode 100644 frontend/tsconfig.desktop.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 rust-toolchain.toml create mode 100644 scripts/release-manager/.env.example create mode 100644 scripts/release-manager/.gitignore create mode 100644 scripts/release-manager/bun.lock create mode 100644 scripts/release-manager/cli.ts create mode 100644 scripts/release-manager/manifest.json create mode 100644 scripts/release-manager/package.json create mode 100644 scripts/release-manager/pnpm-workspace.yaml create mode 100644 scripts/release-manager/tsconfig.json create mode 100644 scripts/release-manager/types.ts create mode 100644 scripts/release-manager/utils.ts diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..9bd17ea --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.wasm32-unknown-unknown] +rustflags = ['--cfg', 'getrandom_backend="wasm_js"', '--cfg=web_sys_unstable_apis'] + +# for Linux +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = ["-C", "link-arg=-fuse-ld=lld"] + +# for Windows +# [target.x86_64-pc-windows-msvc] +# linker = "rust-lld.exe" diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml new file mode 100644 index 0000000..1986317 --- /dev/null +++ b/.github/workflows/builds.yml @@ -0,0 +1,177 @@ +name: Builds +on: + - push +env: + CARGO_TERM_COLOR: always + RUST_VERSION: "stable" +jobs: + desktop: + name: Desktop (${{ matrix.target.os }} / ${{ matrix.target.arch }}) + runs-on: ${{ matrix.target.runner }} + strategy: + fail-fast: false + matrix: + target: + - os: linux + arch: x86_64 + runner: ubuntu-22.04 + rust_target: x86_64-unknown-linux-gnu + - os: macos + arch: x86_64 + runner: macos-15-intel + rust_target: x86_64-apple-darwin + - os: macos + arch: aarch64 + runner: macos-latest + rust_target: aarch64-apple-darwin + - os: windows + arch: x86_64 + runner: windows-latest + rust_target: x86_64-pc-windows-msvc + - os: windows + arch: aarch64 + runner: windows-latest + rust_target: aarch64-pc-windows-msvc + steps: + - uses: actions/checkout@v5 + - name: Setup Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + components: rustfmt, clippy + targets: ${{ matrix.target.rust_target }} + - name: Rust cache + uses: Swatinem/rust-cache@v2 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile --prefer-offline + working-directory: frontend + - name: Cache Tauri CLI (macOS only) + if: matrix.target.os == 'macos' + id: cache-tauri-cli + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/cargo-tauri + key: ${{ runner.os }}-${{ runner.arch }}-tauri-cli-2 + - name: Install cargo-binstall + if: matrix.target.os != 'macos' + uses: taiki-e/install-action@cargo-binstall + - name: Install Tauri CLI (via binstall) + if: matrix.target.os != 'macos' + run: cargo binstall tauri-cli --version '^2' --no-confirm + - name: Install Tauri CLI (from source on macOS) + if: matrix.target.os == 'macos' && steps.cache-tauri-cli.outputs.cache-hit != 'true' + run: cargo install tauri-cli --version '^2' --locked + env: + CARGO_PROFILE_RELEASE_LTO: false + - name: Cache apt packages + if: matrix.target.os == 'linux' + uses: actions/cache@v4 + with: + path: | + /var/cache/apt/archives/*.deb + key: ${{ matrix.target.runner }}-apt-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ matrix.target.runner }}-apt- + - name: Install Linux dependencies + if: matrix.target.os == 'linux' + run: | + sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends \ + build-essential \ + libglib2.0-dev \ + libwebkit2gtk-4.1-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + patchelf + - name: Build desktop app + run: cargo tauri build --target ${{ matrix.target.rust_target }} + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: iron-borders-${{ matrix.target.os }}-${{ matrix.target.arch }} + path: | + target/${{ matrix.target.rust_target }}/release/*.exe + target/${{ matrix.target.rust_target }}/release/bundle/appimage/*.AppImage + target/${{ matrix.target.rust_target }}/release/bundle/deb/*.deb + target/${{ matrix.target.rust_target }}/release/bundle/rpm/*.rpm + target/${{ matrix.target.rust_target }}/release/bundle/dmg/*.dmg + if-no-files-found: ignore + browser: + name: Browser (WASM) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + targets: x86_64-unknown-linux-gnu, wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile --prefer-offline + working-directory: frontend + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@0.2.104 + - name: Install wasm-opt + uses: taiki-e/install-action@v2 + with: + tool: wasm-opt@0.116.1 + - name: Build WASM release + run: | + cargo build -p borders-wasm --profile wasm-release --target wasm32-unknown-unknown + wasm-bindgen --out-dir pkg --out-name borders --target web target/wasm32-unknown-unknown/wasm-release/borders_wasm.wasm + wasm-opt -Oz --enable-bulk-memory --enable-threads --all-features pkg/borders_bg.wasm -o pkg/borders_bg.wasm + mkdir -p frontend/pkg + cp -r pkg/* frontend/pkg/ + - name: Build frontend (root-based) + run: pnpm run build:browser + working-directory: frontend + - name: Upload browser artifacts + uses: actions/upload-artifact@v4 + with: + name: iron-borders-browser + path: frontend/dist/browser/client/**/* + if-no-files-found: error + deploy-cloudflare: + name: Deploy to Cloudflare Pages + needs: browser + runs-on: ubuntu-latest + if: github.event_name == 'push' + steps: + - name: Download browser artifact + uses: actions/download-artifact@v4 + with: + name: iron-borders-browser + path: dist + - name: Deploy to Cloudflare Pages + id: deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy dist --project-name=borders + - name: Print deployment URL + env: + DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }} + run: echo "Deployed to $DEPLOYMENT_URL" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..4a98da5 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,84 @@ +name: Quality +on: + - push +env: + CARGO_TERM_COLOR: always + RUST_VERSION: "stable" +jobs: + rust-quality: + name: Rust Quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + targets: x86_64-unknown-linux-gnu, wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: Cache apt packages + uses: actions/cache@v4 + with: + path: | + /var/cache/apt/archives/*.deb + key: ubuntu-latest-apt-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ubuntu-latest-apt- + - name: Install Linux dependencies + run: | + sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends \ + build-essential \ + libglib2.0-dev \ + libwebkit2gtk-4.1-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + patchelf + - name: Install just + uses: taiki-e/install-action@just + - name: Install cargo-machete + uses: taiki-e/install-action@cargo-machete + - name: Run Rust checks + run: just --shell bash --shell-arg -c check + - name: Install cargo-audit + uses: taiki-e/install-action@cargo-audit + - name: Run security audit + run: cargo audit + frontend-quality: + name: Frontend Quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} + targets: x86_64-unknown-linux-gnu, wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@0.2.104 + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile + working-directory: frontend + - name: Install just + uses: taiki-e/install-action@just + - name: Build WASM for frontend checks + run: | + cargo build -p borders-wasm --profile wasm-dev --target wasm32-unknown-unknown + wasm-bindgen --out-dir pkg --out-name borders --target web target/wasm32-unknown-unknown/wasm-dev/borders_wasm.wasm + mkdir -p frontend/pkg + cp -r pkg/* frontend/pkg/ + - name: Run frontend TypeScript checks + run: pnpm run build:browser + working-directory: frontend diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32ce3d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +target/ +pkg/ +*.pem +/*.io/ +releases/ + +# Build-copied shader files +frontend/public/assets/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.source-commit b/.source-commit new file mode 100644 index 0000000..3b09b6a --- /dev/null +++ b/.source-commit @@ -0,0 +1 @@ +53540d0ae699b9e373e82f86a4a05da4fbad4f34 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d7b005a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7809 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_type_match" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "av1-grain" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b715a6010afb9e457ca2b7c9d2b9c344baa8baed7b38dc476034c171b32575" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libloading 0.8.8", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bevy_ecs" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d929d32190cfcde6efd2df493601c4dbc18a691fd9775a544c951c3c112e1a" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags 2.9.4", + "bumpalo", + "concurrent-queue", + "derive_more 2.0.1", + "fixedbitset", + "indexmap 2.11.4", + "log", + "nonmax", + "serde", + "slotmap", + "smallvec", + "thiserror 2.0.17", + "tracing", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eeddfb80a2e000663e87be9229c26b4da92bddbc06c8776bc0d1f4a7f679079" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17dbc3f8948da58b3c17767d20fd3cd35fe4721ed19a9a3204a6f1d6c9951bdd" +dependencies = [ + "parking_lot", + "proc-macro2", + "quote", + "syn 2.0.106", + "toml_edit 0.23.6", +] + +[[package]] +name = "bevy_platform" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cf8cda162688c95250e74cffaa1c3a04597f105d4ca35554106f107308ea57" +dependencies = [ + "critical-section", + "foldhash", + "futures-channel", + "hashbrown 0.16.0", + "js-sys", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin 0.10.0", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_ptr" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28ab4074e7b781bab84e9b0a41ede245d673d1f75646ce0db27643aedcfb3a85" + +[[package]] +name = "bevy_reflect" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333df3f5947b7e62728eb5c0b51d679716b16c7c5283118fed4563f13230954e" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more 2.0.1", + "disqualified", + "downcast-rs", + "erased-serde", + "foldhash", + "glam", + "serde", + "smallvec", + "smol_str", + "thiserror 2.0.17", + "uuid", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0205dce9c5a4d8d041b263bcfd96e9d9d6f3d49416e12db347ab5778b3071fe1" +dependencies = [ + "bevy_macro_utils", + "indexmap 2.11.4", + "proc-macro2", + "quote", + "syn 2.0.106", + "uuid", +] + +[[package]] +name = "bevy_tasks" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18839182775f30d26f0f84d9de85d25361bb593c99517a80b64ede6cbaf41adc" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "bevy_platform", + "crossbeam-queue", + "derive_more 2.0.1", + "futures-lite", + "heapless", + "pin-project", +] + +[[package]] +name = "bevy_utils" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080254083c74d5f6eb0649d7cd6181bda277e8fe3c509ec68990a5d56ec23f24" +dependencies = [ + "bevy_platform", + "disqualified", + "thread_local", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.106", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +dependencies = [ + "serde", +] + +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.3", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borders-core" +version = "0.6.1" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", + "bevy_ecs", + "bincode", + "chrono", + "directories", + "flume", + "futures", + "futures-lite", + "glam", + "gloo-timers", + "hex", + "hickory-resolver", + "hmac", + "image", + "js-sys", + "machineid-rs", + "once_cell", + "pem", + "rand 0.9.2", + "reqwest", + "ring", + "rustls-pemfile", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "slotmap", + "sysinfo 0.33.1", + "tokio", + "tracing", + "url", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "web-transport", + "winreg 0.52.0", +] + +[[package]] +name = "borders-desktop" +version = "0.6.1" +dependencies = [ + "bevy_ecs", + "borders-core", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-opener", + "tauri-plugin-process", + "tokio", + "tracing", + "tracing-subscriber", + "tracing-tracy", + "tracy-client", +] + +[[package]] +name = "borders-server" +version = "0.6.1" +dependencies = [ + "anyhow", + "borders-core", + "flume", + "glam", + "tokio", + "tracing", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "borders-wasm" +version = "0.6.1" +dependencies = [ + "bevy_ecs", + "borders-core", + "console_error_panic_hook", + "getrandom 0.3.3", + "gloo-timers", + "js-sys", + "lazy_static", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-subscriber", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-tracing", + "web-time", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "built" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.4", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.7", +] + +[[package]] +name = "cc" +version = "1.2.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.8", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "deranged" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.106", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "dlopen2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embed-resource" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.7", + "vswhom", + "winreg 0.55.0", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastbloom" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" +dependencies = [ + "getrandom 0.3.3", + "libm", + "rand 0.9.2", + "siphasher 1.0.1", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.61.3", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glam" +version = "0.30.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" +dependencies = [ + "serde_core", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.9.4", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.11.4", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h3" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfb059a4f28a66f186ed16ad912d142f490676acba59353831d7cb45a96b0d3" +dependencies = [ + "bytes", + "fastrand", + "futures-util", + "http", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "h3-quinn" +version = "0.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d482318ae94198fc8e3cbb0b7ba3099c865d744e6ec7c62039ca7b6b6c66fbf" +dependencies = [ + "bytes", + "futures", + "h3", + "quinn", + "tokio", + "tokio-util", +] + +[[package]] +name = "half" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "equivalent", + "serde", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "bytes", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "h2", + "h3", + "h3-quinn", + "http", + "idna", + "ipnet", + "once_cell", + "pin-project-lite", + "quinn", + "rand 0.9.2", + "ring", + "rustls", + "thiserror 2.0.17", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "quinn", + "rand 0.9.2", + "resolv-conf", + "rustls", + "smallvec", + "thiserror 2.0.17", + "tokio", + "tokio-rustls", + "tracing", + "webpki-roots 0.26.11", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.2", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.0", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.4", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.11.4", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.5", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.9.4", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "machineid-rs" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ceb4d434d69d7199abc3036541ba6ef86767a4356e3077d5a3419f85b70b14" +dependencies = [ + "hex", + "hmac", + "md-5", + "serde", + "serde_json", + "sha-1", + "sha2", + "sysinfo 0.29.11", + "uuid", + "whoami", + "winreg 0.11.0", + "wmi", +] + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "moka" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "moxcms" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc7d85f3d741164e8972ad355e26ac6e51b20fcae5f911c7da8f2d8bbbb3f33" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "png 0.17.16", + "serde", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.4", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "libc", + "objc2 0.6.3", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2 0.6.3", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.9.4", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "libc", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-javascript-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" +dependencies = [ + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.4", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-javascript-core", + "objc2-security", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.11.4", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.9.4", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.6", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pxfm" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.0", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "fastbloom", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.0", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand 0.8.5", + "rand_chacha 0.3.1", + "simd_helpers", + "system-deps", + "thiserror 1.0.69", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "regex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "async-compression", + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 1.0.2", +] + +[[package]] +name = "resolv-conf" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" + +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.106", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more 0.99.20", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.11.4", + "schemars 0.9.0", + "schemars 1.0.4", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics", + "foreign-types", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "sysinfo" +version = "0.29.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.23", + "version-compare", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tao" +version = "0.34.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.3", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.17", + "tokio", + "tray-icon", + "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows 0.61.3", +] + +[[package]] +name = "tauri-build" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.7", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.106", + "tauri-utils", + "thiserror 2.0.17", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.7", + "walkdir", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "url", + "windows 0.61.3", + "zbus", +] + +[[package]] +name = "tauri-plugin-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7461c622a5ea00eb9cd9f7a08dbd3bf79484499fd5c21aa2964677f64ca651ab" +dependencies = [ + "tauri", + "tauri-plugin", +] + +[[package]] +name = "tauri-runtime" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2 0.6.3", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.61.3", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.61.3", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.17", + "toml 0.9.7", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" +dependencies = [ + "embed-resource", + "toml 0.9.7", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +dependencies = [ + "indexmap 2.11.4", + "serde_core", + "serde_spanned 1.0.2", + "toml_datetime 0.7.2", + "toml_parser", + "toml_writer", + "winnow 0.7.13", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.11.4", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.11.4", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.11.4", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow 0.7.13", +] + +[[package]] +name = "toml_edit" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" +dependencies = [ + "indexmap 2.11.4", + "toml_datetime 0.7.2", + "toml_parser", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow 0.7.13", +] + +[[package]] +name = "toml_writer" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-tracy" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracy-client", +] + +[[package]] +name = "tracy-client" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef54005d3d760186fd662dad4b7bb27ecd5531cdef54d1573ebd3f20a9205ed7" +dependencies = [ + "loom", + "once_cell", + "tracy-client-sys", +] + +[[package]] +name = "tracy-client-sys" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319c70195101a93f56db4c74733e272d720768e13471f400c78406a326b172b0" +dependencies = [ + "cc", + "windows-targets 0.52.6", +] + +[[package]] +name = "tray-icon" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "once_cell", + "png 0.17.16", + "serde", + "thiserror 2.0.17", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-tracing" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ab253baf6d3772bbdb37a0966b67d37ab80657ccd1a084b4d7b3de3232375d" +dependencies = [ + "tracing", + "tracing-log", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "web-streams" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c4d5dbf19463c4b65e974303d453cc11991873c7a4a4953214f791d73303a2" +dependencies = [ + "thiserror 2.0.17", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-transport" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03efb036f13785e3cbb9285e9fb94872a0887a9f4e7efeb02647cc12f5e55eea" +dependencies = [ + "bytes", + "thiserror 2.0.17", + "url", + "web-transport-quinn", + "web-transport-wasm", +] + +[[package]] +name = "web-transport-proto" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb650c577c46254d16041c7fe0dc9901d9a42df3f46e77e9d05d1b3c17294b19" +dependencies = [ + "bytes", + "http", + "thiserror 2.0.17", + "url", +] + +[[package]] +name = "web-transport-quinn" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccba8a1ab2aae70119c9ada57e4a2324ebe1820a18814ca47a818f01dcd00f46" +dependencies = [ + "bytes", + "futures", + "http", + "log", + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.17", + "tokio", + "url", + "web-transport-proto", + "web-transport-trait", +] + +[[package]] +name = "web-transport-trait" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850148841799c83f033f4dddccb219f1f097aff6db1bda5b0d3be69fefb32bd" +dependencies = [ + "bytes", +] + +[[package]] +name = "web-transport-wasm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b2af1bed400caa442d75217bb7ece5e1ec853daf60affd5d2448c0a2063662" +dependencies = [ + "bytes", + "js-sys", + "thiserror 2.0.17", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-streams", + "web-sys", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement 0.60.2", + "windows-interface 0.59.3", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.17", + "windows 0.61.3", + "windows-core 0.61.2", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "wgpu-types" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "js-sys", + "log", + "serde", + "thiserror 2.0.17", + "web-sys", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-implement 0.48.0", + "windows-interface 0.48.0", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wmi" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daffb44abb7d2e87a1233aa17fdbde0d55b890b32a23a1f908895b87fa6f1a00" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror 1.0.69", + "windows 0.48.0", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wry" +version = "0.53.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6" +dependencies = [ + "base64 0.22.1", + "block2 0.6.2", + "cookie", + "crossbeam-channel", + "dirs", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "windows-sys 0.60.2", + "winnow 0.7.13", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.13", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.13", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.106", + "winnow 0.7.13", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f2d97a8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,61 @@ +[workspace] +members = [ + "crates/borders-core", + "crates/borders-desktop", + "crates/borders-wasm", + "crates/borders-server", +] +resolver = "2" + +[workspace.package] +authors = ["Xevion"] +edition = "2024" +version = "0.6.1" + +# Enable a small amount of optimization in the dev profile. +[profile.dev] +opt-level = 1 + + +# Enable a large amount of optimization in the dev profile for dependencies. +[profile.dev.package."*"] +opt-level = 3 + +[profile.dev.package.borders-desktop] +opt-level = 1 + +[profile.dev.package.borders-wasm] +opt-level = 1 + +[profile.dev.package.borders-server] +opt-level = 1 + +[profile.dev.package.borders-core] +opt-level = 1 + +# Enable more optimization in the release profile at the cost of compile time. +[profile.release] +# Compile the entire crate as one unit. +# Slows compile times, marginal improvements. +codegen-units = 1 +# Do a second optimization pass over the entire program, including dependencies. +# Slows compile times, marginal improvements. +lto = "thin" + + +# Development profile for WASM builds (faster compile times) +[profile.wasm-dev] +inherits = "dev" +opt-level = 1 +panic = "abort" + +# Size optimization profile for WASM builds +[profile.wasm-release] +inherits = "release" +incremental = false +debug = false +opt-level = "s" # Optimize for size +lto = true # Link-time optimization +codegen-units = 1 # Single codegen unit for better optimization +panic = "abort" # Smaller panic implementation +strip = true # Remove debug symbols diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..877cb88 --- /dev/null +++ b/Justfile @@ -0,0 +1,92 @@ +set shell := ["powershell"] + +default: + just --list + +check: + @echo "Running clippy (native)..." + @cargo clippy --all-targets --all-features --workspace -- -D warnings + @echo "Running cargo check (native)..." + @cargo check --all-targets --all-features --workspace + @echo "Running clippy (wasm32-unknown-unknown)..." + @cargo clippy --target wasm32-unknown-unknown --all-features -p borders-wasm -- -D warnings + @echo "Running cargo check (wasm32-unknown-unknown)..." + @cargo check --target wasm32-unknown-unknown --all-features -p borders-wasm + @echo "Running cargo machete..." + @cargo machete --with-metadata + @echo "All checks passed" + +check-ts: + @just _wasm-build wasm-dev + @echo "Running frontend checks..." + @pnpm run -C frontend check + +fix: + @echo "Running cargo fix..." + cargo fix --all-targets --all-features --workspace --allow-dirty + +wasm-dev: wasm-dev-build + pnpm -C frontend dev:browser --port 1421 + +# Build WASM with the specified profile (wasm-dev or wasm-release) +_wasm-build profile: + @$profile = "{{ profile }}"; \ + $wasmFile = "target/wasm32-unknown-unknown/$profile/borders_wasm.wasm"; \ + $pkgJs = "pkg/borders.js"; \ + $pkgWasm = "pkg/borders_bg.wasm"; \ + $frontendPkgJs = "frontend/pkg/borders.js"; \ + $frontendPkgWasm = "frontend/pkg/borders_bg.wasm"; \ + $beforeTime = if (Test-Path $wasmFile) { (Get-Item $wasmFile).LastWriteTime } else { $null }; \ + cargo build -p borders-wasm --profile $profile --target wasm32-unknown-unknown; \ + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; \ + $afterTime = if (Test-Path $wasmFile) { (Get-Item $wasmFile).LastWriteTime } else { $null }; \ + $wasRebuilt = ($beforeTime -eq $null) -or ($afterTime -ne $beforeTime); \ + $pkgExists = (Test-Path $pkgJs) -and (Test-Path $pkgWasm); \ + $frontendPkgExists = (Test-Path $frontendPkgJs) -and (Test-Path $frontendPkgWasm); \ + $isRelease = $profile -eq "wasm-release"; \ + if ($wasRebuilt -or -not $pkgExists -or ($isRelease -and -not $frontendPkgExists)) { \ + Write-Host "Running wasm-bindgen..."; \ + wasm-bindgen --out-dir pkg --out-name borders --target web $wasmFile; \ + if ($isRelease) { \ + Write-Host "Running wasm-opt..."; \ + wasm-opt -Oz --enable-bulk-memory --enable-threads --all-features pkg/borders_bg.wasm -o pkg/borders_bg.wasm; \ + }; \ + } else { \ + Write-Host "WASM not rebuilt, skipping wasm-bindgen"; \ + } \ + New-Item -ItemType Directory -Force -Path 'frontend/pkg' | Out-Null; \ + Copy-Item -Recurse -Force 'pkg/*' 'frontend/pkg/'; \ + if ($isRelease) { \ + Write-Host "Running frontend build..."; \ + pnpm -C frontend build:browser; \ + }; \ + +# Development WASM build, unoptimized +wasm-dev-build: + @just _wasm-build wasm-dev + +# Release WASM build, optimized +wasm-release-build: + @just _wasm-build wasm-release + +wasm-release: wasm-release-build + @echo "Visit http://localhost:8080 to play" + caddy file-server --listen :8080 --root frontend/dist/browser/client --browse + +desktop-release: + cargo tauri build + target/release/borders-desktop.exe + +desktop-dev: + cargo tauri build --debug + target/debug/borders-desktop.exe + +# Run release manager CLI (handles setup specially) +release *args: + @$firstArg = "{{ args }}".Split()[0]; \ + if ($firstArg -eq "setup") { \ + Write-Host "Installing release manager dependencies..."; \ + pnpm install -C scripts/release-manager; \ + } else { \ + pnpm --silent -C scripts/release-manager exec tsx cli.ts {{ args }}; \ + } \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f948eca --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright Ā© 2025 Ryan Walters. All Rights Reserved. + +This software and associated documentation files (the "Software") are proprietary +and confidential. Unauthorized copying, modification, distribution, or use of this +Software, via any medium, is strictly prohibited without the express written +permission of the copyright holder. + +THE SOFTWARE 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 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/crates/borders-core/Cargo.toml b/crates/borders-core/Cargo.toml new file mode 100644 index 0000000..151a7d7 --- /dev/null +++ b/crates/borders-core/Cargo.toml @@ -0,0 +1,91 @@ +[package] +name = "borders-core" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[package.metadata.cargo-machete] +ignored = ["serde_bytes", "chrono"] + +[features] +default = ["ui"] +bevy_debug = ["bevy_ecs/detailed_trace"] +ui = [] + +[dependencies] +anyhow = "1.0" +async-trait = "0.1" +bevy_ecs = { version = "0.17", default-features = false, features = ["std"] } +bincode = { version = "2.0.1", features = ["serde"] } +flume = "0.11" +futures = "0.3" +futures-lite = "2.6.1" +glam = { version = "0.30", features = ["serde"] } +hex = "0.4" +hmac = "0.12" +image = "0.25" +once_cell = "1.20" +rand = "0.9" +serde = { version = "1.0", features = ["derive", "rc"] } +slotmap = "1.0" +serde_bytes = "0.11" +serde_json = "1.0" +sha2 = "0.10" +tracing = "0.1" +url = "2.5.0" +web-transport = "0.9" +base64 = "0.22" + +# Target-specific dependencies to keep WASM builds compatible +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", features = [ + "rt-multi-thread", + "macros", + "time", + "io-util", + "sync", +] } +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "rustls-tls", + "brotli", + "gzip", + "deflate", + "zstd", +] } +hickory-resolver = { version = "0.25", features = [ + "tls-ring", + "https-ring", + "quic-ring", + "h3-ring", + "webpki-roots", +] } +uuid = { version = "1.11", features = ["v4", "serde"] } +machineid-rs = "1.2" +directories = "5.0" +rustls-pemfile = "2.2.0" +ring = "0.17.14" +pem = "3.0.5" +sysinfo = "0.33" + +[target.'cfg(windows)'.dependencies] +winreg = "0.52" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1", features = ["rt", "macros", "time", "io-util"] } +reqwest = { version = "0.12", default-features = false, features = ["json"] } +uuid = { version = "1.11", features = ["v4", "serde", "js"] } +js-sys = "0.3" +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +gloo-timers = { version = "0.3", features = ["futures"] } +web-sys = { version = "0.3", features = [ + "BroadcastChannel", + "MessageEvent", + "Navigator", + "Window", +] } +web-time = "1.1" + +[build-dependencies] +chrono = "0.4" diff --git a/crates/borders-core/assets/maps/World.json b/crates/borders-core/assets/maps/World.json new file mode 100644 index 0000000..d4ff1ee --- /dev/null +++ b/crates/borders-core/assets/maps/World.json @@ -0,0 +1,76 @@ +{ + "tiles": [ + { + "color": "#000000", + "name": "Water", + "colorBase": "water", + "colorVariant": 4, + "conquerable": false, + "navigable": true + }, + { + "color": "#222222", + "name": "Water", + "colorBase": "water", + "colorVariant": 6, + "conquerable": false, + "navigable": true + }, + { + "color": "#555555", + "name": "Water", + "colorBase": "water", + "colorVariant": 12, + "conquerable": false, + "navigable": true + }, + { + "color": "#777777", + "name": "Water", + "colorBase": "water", + "colorVariant": 14, + "conquerable": false, + "navigable": true + }, + { + "color": "#999999", + "name": "Land", + "colorBase": "mountain", + "colorVariant": 5, + "conquerable": true, + "navigable": false, + "expansionCost": 80, + "expansionTime": 80 + }, + { + "color": "#BBBBBB", + "name": "Land", + "colorBase": "mountain", + "colorVariant": 9, + "conquerable": true, + "navigable": false, + "expansionCost": 70, + "expansionTime": 70 + }, + { + "color": "#DDDDDD", + "name": "Land", + "colorBase": "grass", + "colorVariant": 9, + "conquerable": true, + "navigable": false, + "expansionCost": 60, + "expansionTime": 60 + }, + { + "color": "#FFFFFF", + "name": "Land", + "colorBase": "grass", + "colorVariant": 6, + "conquerable": true, + "navigable": false, + "expansionCost": 50, + "expansionTime": 50 + } + ] +} diff --git a/crates/borders-core/assets/maps/World.png b/crates/borders-core/assets/maps/World.png new file mode 100644 index 0000000000000000000000000000000000000000..cbaa74c1d2d095c46b3fc1fa0cdac3b22f8e4e52 GIT binary patch literal 189026 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 SCjdpr_|ffxig3LGEzy>ixo6by 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∨#UfA!jG4D}t@$4U);m^2$b^j`* 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 literal 0 HcmV?d00001 diff --git a/crates/borders-core/build.rs b/crates/borders-core/build.rs new file mode 100644 index 0000000..01cc5b9 --- /dev/null +++ b/crates/borders-core/build.rs @@ -0,0 +1,74 @@ +use std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + // Get the workspace root (two levels up from borders-core) + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let workspace_root = manifest_dir.parent().unwrap().parent().unwrap(); + + // Determine if we're in production mode (CI or release profile) + let is_production = env::var("CI").is_ok() || env::var("PROFILE").map(|p| p == "release").unwrap_or(false); + + // Read git commit from .source-commit file + let source_commit_path = workspace_root.join(".source-commit"); + let git_commit = if source_commit_path.exists() { + match fs::read_to_string(&source_commit_path) { + Ok(content) => content.trim().to_string(), + Err(e) if is_production => { + panic!("Failed to read .source-commit file in production: {}", e); + } + Err(_) => "unknown".to_string(), + } + } else { + // Fallback to git command if file doesn't exist (local development) + let git_result = std::process::Command::new("git").args(["rev-parse", "HEAD"]).current_dir(workspace_root).output().ok().and_then(|output| if output.status.success() { String::from_utf8(output.stdout).ok() } else { None }).map(|s| s.trim().to_string()); + + match git_result { + Some(commit) => commit, + None if is_production => { + panic!("Failed to acquire git commit in production and .source-commit file does not exist"); + } + None => "unknown".to_string(), + } + }; + + // Determine build time based on environment + let build_time = if let Ok(epoch) = env::var("SOURCE_DATE_EPOCH") { + // Use provided timestamp for reproducible builds + match epoch.parse::().ok().and_then(|ts| chrono::DateTime::from_timestamp(ts, 0)).map(|dt| dt.to_rfc3339()) { + Some(time) => time, + None if is_production => { + panic!("Failed to parse SOURCE_DATE_EPOCH in production: {}", epoch); + } + None => "unknown".to_string(), + } + } else if env::var("CI").is_ok() { + // Generate fresh timestamp in CI + chrono::Utc::now().to_rfc3339() + } else { + // Static value for local development + "dev".to_string() + }; + + // Set environment variables for compile-time access + println!("cargo:rustc-env=BUILD_GIT_COMMIT={}", git_commit); + println!("cargo:rustc-env=BUILD_TIME={}", build_time); + + // Only re-run the build script when specific files change + println!("cargo:rerun-if-changed=build.rs"); + + // In CI, watch the .source-commit file if it exists + if source_commit_path.exists() { + println!("cargo:rerun-if-changed={}", source_commit_path.display()); + } + + // In local development, watch .git/HEAD to detect branch switches + // We intentionally don't watch the branch ref file to avoid spurious rebuilds + if env::var("CI").is_err() { + let git_head = workspace_root.join(".git").join("HEAD"); + if git_head.exists() { + println!("cargo:rerun-if-changed={}", git_head.display()); + } + } +} diff --git a/crates/borders-core/src/app.rs b/crates/borders-core/src/app.rs new file mode 100644 index 0000000..c09b23c --- /dev/null +++ b/crates/borders-core/src/app.rs @@ -0,0 +1,146 @@ +//! Minimal ECS app wrapper to replace Bevy's App + +use bevy_ecs::message::{Message, Messages}; +use bevy_ecs::prelude::*; +use bevy_ecs::schedule::{IntoScheduleConfigs, ScheduleLabel, Schedules}; +use bevy_ecs::system::ScheduleSystem; +use std::fmt::Debug; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct Startup; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct Update; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +pub struct Last; + +pub struct App { + world: World, +} + +impl App { + pub fn new() -> Self { + let mut world = World::new(); + + // Initialize schedules with proper ordering + let mut schedules = Schedules::new(); + schedules.insert(Schedule::new(Startup)); + schedules.insert(Schedule::new(Update)); + schedules.insert(Schedule::new(Last)); + + world.insert_resource(schedules); + + Self { world } + } + + pub fn world(&self) -> &World { + &self.world + } + + pub fn world_mut(&mut self) -> &mut World { + &mut self.world + } + + pub fn insert_resource(&mut self, resource: R) -> &mut Self { + self.world.insert_resource(resource); + self + } + + pub fn init_resource(&mut self) -> &mut Self { + self.world.init_resource::(); + self + } + + pub fn insert_non_send_resource(&mut self, resource: R) -> &mut Self { + self.world.insert_non_send_resource(resource); + self + } + + pub fn add_message(&mut self) -> &mut Self { + if !self.world.contains_resource::>() { + self.world.init_resource::>(); + + // Add system to update this message type each frame + self.add_systems(Last, |mut messages: ResMut>| { + messages.update(); + }); + } + self + } + + pub fn add_systems(&mut self, schedule: impl ScheduleLabel, systems: impl IntoScheduleConfigs) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + if let Some(schedule_inst) = schedules.get_mut(schedule) { + schedule_inst.add_systems(systems); + } + self + } + + pub fn update(&mut self) { + let _guard = tracing::trace_span!("app_update").entered(); + + // Remove schedules temporarily to avoid resource_scope conflicts + let mut schedules = self.world.remove_resource::().unwrap(); + + // Run Update schedule + if let Some(schedule) = schedules.get_mut(Update) { + let _guard = tracing::trace_span!("update_schedule").entered(); + schedule.run(&mut self.world); + } + + // Run Last schedule (includes event updates) + if let Some(schedule) = schedules.get_mut(Last) { + let _guard = tracing::trace_span!("last_schedule").entered(); + schedule.run(&mut self.world); + } + + // Re-insert schedules + self.world.insert_resource(schedules); + } + + pub fn run_startup(&mut self) { + let _guard = tracing::trace_span!("run_startup_schedule").entered(); + + // Remove schedules temporarily to avoid resource_scope conflicts + let mut schedules = self.world.remove_resource::().unwrap(); + + // Run Startup schedule + if let Some(schedule) = schedules.get_mut(Startup) { + schedule.run(&mut self.world); + } + + // Re-insert schedules + self.world.insert_resource(schedules); + } + + pub fn finish(&mut self) { + // Finalize schedules + let mut schedules = self.world.remove_resource::().unwrap(); + + let system_count: usize = schedules.iter().map(|(_, schedule)| schedule.systems().map(|iter| iter.count()).unwrap_or(0)).sum(); + + let _guard = tracing::trace_span!("finish_schedules", system_count = system_count).entered(); + + for (_, schedule) in schedules.iter_mut() { + schedule.graph_mut().initialize(&mut self.world); + } + + self.world.insert_resource(schedules); + } + + pub fn cleanup(&mut self) { + // Any cleanup needed before running + } +} + +impl Default for App { + fn default() -> Self { + Self::new() + } +} + +/// Plugin trait for modular setup +pub trait Plugin { + fn build(&self, app: &mut App); +} diff --git a/crates/borders-core/src/build_info.rs b/crates/borders-core/src/build_info.rs new file mode 100644 index 0000000..87f15f2 --- /dev/null +++ b/crates/borders-core/src/build_info.rs @@ -0,0 +1,21 @@ +//! Build metadata injected at compile time + +/// The version of the application from Cargo.toml +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// The git commit hash from .source-commit file or git command +pub const GIT_COMMIT: &str = env!("BUILD_GIT_COMMIT"); + +/// The build timestamp in RFC3339 format (UTC) +pub const BUILD_TIME: &str = env!("BUILD_TIME"); + +/// Get the git commit hash (short form, first 7 characters) +pub fn git_commit_short() -> &'static str { + let full = GIT_COMMIT; + if full.len() >= 7 { &full[..7] } else { full } +} + +/// Full build information formatted as a string +pub fn info() -> String { + format!("Iron Borders v{} ({})\nBuilt: {}", VERSION, git_commit_short(), BUILD_TIME) +} diff --git a/crates/borders-core/src/game/ai/bot.rs b/crates/borders-core/src/game/ai/bot.rs new file mode 100644 index 0000000..15da240 --- /dev/null +++ b/crates/borders-core/src/game/ai/bot.rs @@ -0,0 +1,483 @@ +use std::collections::{HashMap, HashSet}; + +use bevy_ecs::prelude::*; +use glam::U16Vec2; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; + +use crate::game::core::action::GameAction; +use crate::game::core::constants::bot::*; +use crate::game::world::TerritoryManager; +use crate::utils::neighbors; + +/// Simple bot AI +pub struct BotPlayer { + last_action_tick: u64, + action_cooldown: u64, +} + +impl Default for BotPlayer { + fn default() -> Self { + Self::new() + } +} + +impl BotPlayer { + pub fn new() -> Self { + let mut rng = rand::rng(); + Self { last_action_tick: 0, action_cooldown: rng.random_range(0..INITIAL_COOLDOWN_MAX) } + } + + /// Sample a random subset of border tiles to reduce O(n) iteration cost + fn sample_border_tiles(border_tiles: &HashSet, border_count: usize, rng: &mut StdRng) -> Vec { + if border_count <= MAX_BORDER_SAMPLES { + border_tiles.iter().copied().collect::>() + } else { + // Random sampling without replacement + let mut sampled = Vec::with_capacity(MAX_BORDER_SAMPLES); + let border_vec: Vec = border_tiles.iter().copied().collect(); + let mut indices: Vec = (0..border_count).collect(); + + for _ in 0..MAX_BORDER_SAMPLES { + let idx = rng.random_range(0..indices.len()); + sampled.push(border_vec[indices.swap_remove(idx)]); + } + sampled + } + } + + /// Tick the bot AI - now deterministic based on turn number and RNG seed + pub fn tick(&mut self, turn_number: u64, player_id: u16, territory_manager: &TerritoryManager, player_borders: &HashMap>, rng_seed: u64) -> Option { + // Only act every few ticks + if turn_number < self.last_action_tick + self.action_cooldown { + return None; + } + + self.last_action_tick = turn_number; + + // Deterministic RNG based on turn number, player ID, and global seed + let seed = rng_seed.wrapping_add(turn_number).wrapping_add(player_id as u64); + let mut rng = StdRng::seed_from_u64(seed); + self.action_cooldown = rng.random_range(ACTION_COOLDOWN_MIN..ACTION_COOLDOWN_MAX); + + // Decide action: expand into wilderness or attack a neighbor + let _guard = tracing::trace_span!("bot_tick", player_id).entered(); + + let action_type: f32 = rng.random(); + + if action_type < EXPAND_PROBABILITY { + // Expand into wilderness (60% chance) + self.expand_wilderness(player_id, territory_manager, player_borders, &mut rng) + } else { + // Attack a neighbor (40% chance) + self.attack_neighbor(player_id, territory_manager, player_borders, &mut rng) + } + } + + /// Expand into unclaimed territory + fn expand_wilderness(&self, player_id: u16, territory_manager: &TerritoryManager, player_borders: &HashMap>, rng: &mut StdRng) -> Option { + let border_tiles = player_borders.get(&player_id)?; + let border_count = border_tiles.len(); + + let _guard = tracing::trace_span!("expand_wilderness", border_count).entered(); + + let size = territory_manager.size(); + + let tiles_to_check = Self::sample_border_tiles(border_tiles, border_count, rng); + + // Find a valid, unclaimed neighbor tile to attack + for &tile in &tiles_to_check { + if let Some(neighbor) = neighbors(tile, size).find(|&neighbor| !territory_manager.is_owner(neighbor, player_id) && !territory_manager.is_water(neighbor)) { + let troop_percentage: f32 = rng.random_range(EXPAND_TROOPS_MIN..EXPAND_TROOPS_MAX); + return Some(GameAction::Attack { player_id, target_tile: neighbor, troops_ratio: troop_percentage }); + } + } + + tracing::trace!(player_id, "No wilderness target found"); + None + } + + /// Attack a neighboring player + fn attack_neighbor(&self, player_id: u16, territory_manager: &TerritoryManager, player_borders: &HashMap>, rng: &mut StdRng) -> Option { + let border_tiles = player_borders.get(&player_id)?; + let border_count = border_tiles.len(); + + let _guard = tracing::trace_span!("attack_neighbor", border_count).entered(); + + // Find neighboring players + let mut neighboring_nations = HashSet::new(); + let size = territory_manager.size(); + + let tiles_to_check = Self::sample_border_tiles(border_tiles, border_count, rng); + + for &tile in &tiles_to_check { + neighboring_nations.extend(neighbors(tile, size).filter_map(|neighbor| { + let ownership = territory_manager.get_ownership(neighbor); + ownership.nation_id().filter(|&nation_id| nation_id != player_id) + })); + } + + if neighboring_nations.is_empty() { + return None; + } + + // Pick a random neighbor to attack + let neighbor_vec: Vec<_> = neighboring_nations.into_iter().collect(); + let target_id = neighbor_vec[rng.random_range(0..neighbor_vec.len())]; + + // To attack a player, we need to pick a specific tile. + // Let's find a border tile of the target player that is adjacent to us. + let target_border = player_borders.get(&target_id)?; + let target_border_count = target_border.len(); + + let target_tiles_to_check = Self::sample_border_tiles(target_border, target_border_count, rng); + + for &target_tile in &target_tiles_to_check { + if neighbors(target_tile, size).any(|neighbor| territory_manager.get_owner(neighbor) == player_id) { + let troop_percentage: f32 = rng.random_range(ATTACK_TROOPS_MIN..ATTACK_TROOPS_MAX); + return Some(GameAction::Attack { player_id, target_tile, troops_ratio: troop_percentage }); + } + } + + None + } +} + +/// Spatial grid for fast spawn collision detection +/// Divides map into cells for O(1) neighbor queries instead of O(n) +struct SpawnGrid { + grid: HashMap<(i32, i32), Vec>, + cell_size: f32, +} + +impl SpawnGrid { + fn new(cell_size: f32, _map_width: u16) -> Self { + Self { grid: HashMap::new(), cell_size } + } + + fn insert(&mut self, pos: U16Vec2) { + let cell = self.pos_to_cell(pos); + self.grid.entry(cell).or_default().push(pos); + } + + fn pos_to_cell(&self, pos: U16Vec2) -> (i32, i32) { + let x = pos.x as f32; + let y = pos.y as f32; + ((x / self.cell_size) as i32, (y / self.cell_size) as i32) + } + + fn has_nearby(&self, pos: U16Vec2, radius: f32) -> bool { + let cell = self.pos_to_cell(pos); + let cell_radius = (radius / self.cell_size).ceil() as i32; + + for dx in -cell_radius..=cell_radius { + for dy in -cell_radius..=cell_radius { + let check_cell = (cell.0 + dx, cell.1 + dy); + if let Some(positions) = self.grid.get(&check_cell) { + for &existing_pos in positions { + if calculate_position_distance(pos, existing_pos) < radius { + return true; + } + } + } + } + } + false + } +} + +/// Calculate Euclidean distance between two positions +fn calculate_position_distance(pos1: U16Vec2, pos2: U16Vec2) -> f32 { + let dx = pos1.x as f32 - pos2.x as f32; + let dy = pos1.y as f32 - pos2.y as f32; + (dx * dx + dy * dy).sqrt() +} + +/// Manager for bot AI state and decision-making +#[derive(Resource)] +pub struct BotManager { + bots: Vec, + bot_player_ids: Vec, +} + +impl BotManager { + /// Create a new BotManager with the specified number of bots + pub fn new(bot_count: usize, human_player_count: usize) -> Self { + let bots = (0..bot_count).map(|_| BotPlayer::new()).collect(); + + // Bot player IDs start after human players + // Human player is ID 0, so first bot is ID 1 + let first_bot_id = human_player_count as u16; + let bot_player_ids = (first_bot_id..(first_bot_id + bot_count as u16)).collect(); + + Self { bots, bot_player_ids } + } + + /// Get the number of bots + pub fn bot_count(&self) -> usize { + self.bots.len() + } + + /// Get bot player IDs + pub fn bot_player_ids(&self) -> &[u16] { + &self.bot_player_ids + } + + /// Calculate initial bot spawn positions (first pass) + /// + /// Places bots at random valid locations with adaptive spacing. + /// Uses spatial grid for O(1) neighbor checks and adaptively reduces + /// minimum distance when map becomes crowded. + /// + /// Guarantees all bots spawn (no silent drops). This is deterministic based on rng_seed. + /// + /// Returns Vec for each bot + pub fn calculate_initial_spawns(&self, territory_manager: &TerritoryManager, rng_seed: u64) -> Vec { + let _guard = tracing::trace_span!("calculate_initial_spawns", bot_count = self.bot_player_ids.len()).entered(); + + let size = territory_manager.size(); + let width = size.x; + + let mut spawn_positions = Vec::with_capacity(self.bot_player_ids.len()); + let mut grid = SpawnGrid::new(MIN_SPAWN_DISTANCE, width); + let mut current_min_distance = MIN_SPAWN_DISTANCE; + + for (bot_index, &player_id) in self.bot_player_ids.iter().enumerate() { + // Deterministic RNG for spawn location + let seed = rng_seed.wrapping_add(player_id as u64).wrapping_add(bot_index as u64); + let mut rng = StdRng::seed_from_u64(seed); + + let mut placed = false; + + // Try with current minimum distance + while !placed && current_min_distance >= ABSOLUTE_MIN_DISTANCE { + // Phase 1: Random sampling + for _ in 0..SPAWN_RANDOM_ATTEMPTS { + let tile_pos = U16Vec2::new(rng.random_range(0..size.x), rng.random_range(0..size.y)); + + // Check if tile is valid land + if territory_manager.has_owner(tile_pos) || territory_manager.is_water(tile_pos) { + continue; + } + + // Check distance using spatial grid (O(1) instead of O(n)) + if !grid.has_nearby(tile_pos, current_min_distance) { + spawn_positions.push(crate::game::SpawnPoint::new(player_id, tile_pos)); + grid.insert(tile_pos); + placed = true; + break; + } + } + + // Phase 2: Grid-guided fallback (if random sampling failed) + if !placed { + // Try a systematic grid search with stride + let stride = (current_min_distance * SPAWN_GRID_STRIDE_FACTOR) as u16; + let mut attempts = 0; + for y in (0..size.y).step_by(stride.max(1) as usize) { + for x in (0..size.x).step_by(stride.max(1) as usize) { + let tile_pos = U16Vec2::new(x, y); + + if territory_manager.has_owner(tile_pos) || territory_manager.is_water(tile_pos) { + continue; + } + + if !grid.has_nearby(tile_pos, current_min_distance) { + spawn_positions.push(crate::game::SpawnPoint::new(player_id, tile_pos)); + grid.insert(tile_pos); + placed = true; + break; + } + + attempts += 1; + if attempts > SPAWN_GRID_MAX_ATTEMPTS { + break; + } + } + if placed { + break; + } + } + } + + // Phase 3: Reduce minimum distance and retry + if !placed { + current_min_distance *= DISTANCE_REDUCTION_FACTOR; + if bot_index % 100 == 0 && current_min_distance < MIN_SPAWN_DISTANCE { + tracing::debug!("Adaptive spawn: reduced min_distance to {:.1} for bot {}", current_min_distance, bot_index); + } + } + } + + // Final fallback: Place at any valid land tile (guaranteed) + if !placed { + for _ in 0..SPAWN_FALLBACK_ATTEMPTS { + let tile_pos = U16Vec2::new(rng.random_range(0..size.x), rng.random_range(0..size.y)); + if !territory_manager.has_owner(tile_pos) && !territory_manager.is_water(tile_pos) { + spawn_positions.push(crate::game::SpawnPoint::new(player_id, tile_pos)); + grid.insert(tile_pos); + placed = true; + tracing::warn!("Bot {} placed with fallback (no distance constraint)", player_id); + break; + } + } + } + + if !placed { + tracing::error!("Failed to place bot {} after all attempts", player_id); + } + } + + spawn_positions + } + + /// Recalculate bot spawns considering player positions (second pass) + /// + /// For any bot that is too close to a player spawn, find a new position. + /// Uses adaptive algorithm with grid acceleration to guarantee all displaced + /// bots find new positions. This maintains determinism while ensuring proper spawn spacing. + /// + /// Arguments: + /// - `initial_bot_spawns`: Bot positions from first pass + /// - `player_spawns`: Human player spawn positions + /// - `territory_manager`: For checking valid tiles + /// - `rng_seed`: For deterministic relocation + /// + /// Returns updated Vec with relocated bots + pub fn recalculate_spawns_with_players(&self, initial_bot_spawns: Vec, player_spawns: &[crate::game::SpawnPoint], territory_manager: &TerritoryManager, rng_seed: u64) -> Vec { + let _guard = tracing::trace_span!("recalculate_spawns_with_players", bot_count = initial_bot_spawns.len(), player_count = player_spawns.len()).entered(); + + let size = territory_manager.size(); + let width = size.x; + + // Build spatial grid from player spawns and bots we're keeping + let mut grid = SpawnGrid::new(MIN_SPAWN_DISTANCE, width); + for spawn in player_spawns { + grid.insert(spawn.tile); + } + + // Identify bots that need relocation + let mut bots_to_relocate = Vec::new(); + let mut final_spawns = Vec::new(); + + for spawn in initial_bot_spawns { + let mut needs_relocation = false; + + // Check distance from all player spawns + for player_spawn in player_spawns { + if calculate_position_distance(spawn.tile, player_spawn.tile) < MIN_SPAWN_DISTANCE { + needs_relocation = true; + break; + } + } + + if needs_relocation { + bots_to_relocate.push(spawn.nation); + } else { + final_spawns.push(spawn); + grid.insert(spawn.tile); + } + } + + // Relocate bots using adaptive algorithm (same as calculate_initial_spawns) + let mut current_min_distance = MIN_SPAWN_DISTANCE; + + for (reloc_index, &player_id) in bots_to_relocate.iter().enumerate() { + let seed = rng_seed.wrapping_add(player_id as u64).wrapping_add(0xDEADBEEF); + let mut rng = StdRng::seed_from_u64(seed); + + let mut placed = false; + + // Try with current minimum distance + while !placed && current_min_distance >= ABSOLUTE_MIN_DISTANCE { + // Phase 1: Random sampling + for _ in 0..SPAWN_RANDOM_ATTEMPTS { + let tile_pos = U16Vec2::new(rng.random_range(0..size.x), rng.random_range(0..size.y)); + + // Check if tile is valid land + if territory_manager.has_owner(tile_pos) || territory_manager.is_water(tile_pos) { + continue; + } + + // Check distance using spatial grid (includes players + placed bots) + if !grid.has_nearby(tile_pos, current_min_distance) { + final_spawns.push(crate::game::SpawnPoint::new(player_id, tile_pos)); + grid.insert(tile_pos); + placed = true; + break; + } + } + + // Phase 2: Grid-guided fallback + if !placed { + let stride = (current_min_distance * 0.8) as u16; + let mut attempts = 0; + for y in (0..size.y).step_by(stride.max(1) as usize) { + for x in (0..size.x).step_by(stride.max(1) as usize) { + let tile_pos = U16Vec2::new(x, y); + + if territory_manager.has_owner(tile_pos) || territory_manager.is_water(tile_pos) { + continue; + } + + if !grid.has_nearby(tile_pos, current_min_distance) { + final_spawns.push(crate::game::SpawnPoint::new(player_id, tile_pos)); + grid.insert(tile_pos); + placed = true; + break; + } + + attempts += 1; + if attempts > SPAWN_GRID_MAX_ATTEMPTS { + break; + } + } + if placed { + break; + } + } + } + + // Phase 3: Reduce minimum distance and retry + if !placed { + current_min_distance *= DISTANCE_REDUCTION_FACTOR; + if reloc_index % 50 == 0 && current_min_distance < MIN_SPAWN_DISTANCE { + tracing::debug!("Adaptive relocation: reduced min_distance to {:.1} for bot {}", current_min_distance, reloc_index); + } + } + } + + // Final fallback: Place at any valid land tile (guaranteed) + if !placed { + for _ in 0..SPAWN_FALLBACK_ATTEMPTS { + let tile_pos = U16Vec2::new(rng.random_range(0..size.x), rng.random_range(0..size.y)); + if !territory_manager.has_owner(tile_pos) && !territory_manager.is_water(tile_pos) { + final_spawns.push(crate::game::SpawnPoint::new(player_id, tile_pos)); + grid.insert(tile_pos); + placed = true; + tracing::warn!("Bot {} relocated with fallback (no distance constraint)", player_id); + break; + } + } + } + + if !placed { + tracing::error!("Failed to relocate bot {} after all attempts", player_id); + } + } + + final_spawns + } + + /// Calculate action for a specific bot + /// + /// This is deterministic - same inputs = same output + #[allow(clippy::too_many_arguments)] + pub fn calculate_action(&mut self, bot_index: usize, turn_number: u64, player_id: u16, _troops: f32, _territory_size: usize, territory_manager: &TerritoryManager, player_borders: &HashMap>, rng_seed: u64) -> Option { + if bot_index >= self.bots.len() { + return None; + } + + self.bots[bot_index].tick(turn_number, player_id, territory_manager, player_borders, rng_seed) + } +} diff --git a/crates/borders-core/src/game/ai/mod.rs b/crates/borders-core/src/game/ai/mod.rs new file mode 100644 index 0000000..ec1a493 --- /dev/null +++ b/crates/borders-core/src/game/ai/mod.rs @@ -0,0 +1,7 @@ +//! AI and bot player logic +//! +//! This module contains the bot manager and AI decision-making logic. + +pub mod bot; + +pub use bot::*; diff --git a/crates/borders-core/src/game/combat/active.rs b/crates/borders-core/src/game/combat/active.rs new file mode 100644 index 0000000..7fbb902 --- /dev/null +++ b/crates/borders-core/src/game/combat/active.rs @@ -0,0 +1,264 @@ +/// Active attacks management +/// +/// This module manages all ongoing attacks in the game. It provides efficient +/// lookup and coordination of attacks, ensuring proper merging of attacks on +/// the same target and handling counter-attacks. +use std::collections::{HashMap, HashSet}; + +use bevy_ecs::prelude::*; +use glam::U16Vec2; +use slotmap::{SlotMap, new_key_type}; + +new_key_type! { + /// Unique key for identifying attacks in the SlotMap + pub struct AttackKey; +} + +use super::executor::{AttackConfig, AttackExecutor}; +use crate::game::core::rng::DeterministicRng; +use crate::game::entities::{HumanPlayerCount, PlayerEntityMap, TerritorySize, Troops}; +use crate::game::world::TerritoryManager; + +/// Manages all active attacks in the game +/// +/// This resource tracks ongoing attacks and provides efficient lookup +/// by attacker/target relationships. Attacks progress over multiple turns +/// until they run out of troops or conquerable tiles. +/// +/// # Index Structures +/// +/// Multiple index structures are maintained for fast lookup: +/// - `player_index`: [attacker][target] → set of attack keys +/// - `unclaimed_index`: [attacker] → attack key for unclaimed territory +/// - `player_attack_list`: [player] → attacks where player is attacker +/// - `target_attack_list`: [player] → attacks where player is target +/// +/// Uses SlotMap for stable keys - no index shifting needed on removal. +/// Uses HashSet for O(1) lookups and removals. +#[derive(Resource)] +pub struct ActiveAttacks { + attacks: SlotMap, + player_index: Vec>>, // [attacker][target] -> set of attack keys + unclaimed_index: Vec>, // [attacker] -> attack key for unclaimed + player_attack_list: Vec>, // [player] -> set of attack keys where player is attacker + target_attack_list: Vec>, // [player] -> set of attack keys where player is target +} + +impl Default for ActiveAttacks { + fn default() -> Self { + Self::new() + } +} + +impl ActiveAttacks { + pub fn new() -> Self { + Self { attacks: SlotMap::with_key(), player_index: Vec::new(), unclaimed_index: Vec::new(), player_attack_list: Vec::new(), target_attack_list: Vec::new() } + } + + /// Initialize the attack handler + pub fn init(&mut self, max_players: usize) { + self.attacks.clear(); + self.player_index = vec![vec![HashSet::new(); max_players]; max_players]; + self.unclaimed_index = vec![None; max_players]; + self.player_attack_list = vec![HashSet::new(); max_players]; + self.target_attack_list = vec![HashSet::new(); max_players]; + } + + /// Schedule an attack on unclaimed territory + /// + /// If an attack on unclaimed territory already exists for this player, + /// the troops are added to it and borders are expanded. + #[allow(clippy::too_many_arguments)] + pub fn schedule_unclaimed(&mut self, player_id: u16, troops: f32, target_tile: U16Vec2, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, player_borders: &HashMap>, turn_number: u64, rng: &DeterministicRng) { + // Check if there's already an attack on unclaimed territory + if let Some(attack_key) = self.unclaimed_index[player_id as usize] { + // Add troops to existing attack + self.attacks[attack_key].modify_troops(troops); + + // Add new borders to allow multi-region expansion + if let Some(borders) = border_tiles.or_else(|| player_borders.get(&player_id).copied()) { + self.attacks[attack_key].add_borders(borders, territory_manager, rng); + } + return; + } + + // Create new attack + self.add_unclaimed(player_id, troops, target_tile, border_tiles, territory_manager, player_borders, turn_number, rng); + } + + /// Schedule an attack on another player + /// + /// Handles attack merging (if attacking same target) and counter-attacks + /// (opposite direction attacks are resolved first). + #[allow(clippy::too_many_arguments)] + pub fn schedule_attack(&mut self, player_id: u16, target_id: u16, target_tile: U16Vec2, mut troops: f32, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, player_borders: &HashMap>, turn_number: u64, rng: &DeterministicRng) { + // Check if there's already an attack on this target + if let Some(&attack_key) = self.player_index[player_id as usize][target_id as usize].iter().next() { + // Add troops to existing attack + self.attacks[attack_key].modify_troops(troops); + + // Add new borders to allow multi-region expansion + if let Some(borders) = border_tiles.or_else(|| player_borders.get(&player_id).copied()) { + self.attacks[attack_key].add_borders(borders, territory_manager, rng); + } + return; + } + + // Check for counter-attacks (opposite direction) - prevent mutual attacks + while !self.player_index[target_id as usize][player_id as usize].is_empty() { + let opposite_key = *self.player_index[target_id as usize][player_id as usize].iter().next().unwrap(); + + if self.attacks[opposite_key].oppose(troops) { + // Counter-attack absorbed the new attack + return; + } + + // Counter-attack was defeated, deduct its troops from the new attack + troops -= self.attacks[opposite_key].get_troops(); + + // Remove the defeated counter-attack + self.remove_attack(opposite_key); + } + + // Create new attack + self.add_attack(player_id, target_id, troops, target_tile, border_tiles, territory_manager, player_borders, turn_number, rng); + } + + /// Tick all active attacks + /// + /// Progresses each attack by one turn. Attacks that run out of troops + /// or conquerable tiles are removed and their remaining troops are + /// returned to the attacking player. + #[allow(clippy::too_many_arguments)] + pub fn tick(&mut self, entity_map: &PlayerEntityMap, players: &mut Query<(&mut Troops, &mut TerritorySize)>, _commands: &mut Commands, territory_manager: &mut TerritoryManager, player_borders: &HashMap>, rng: &DeterministicRng, human_count: &HumanPlayerCount) { + let attack_count = self.attacks.len(); + let _guard = tracing::trace_span!("attacks_tick", attack_count).entered(); + + let mut attacks_to_remove = Vec::new(); + + for (attack_key, attack) in &mut self.attacks { + let should_continue = attack.tick(entity_map, players, territory_manager, player_borders, rng); + + if !should_continue { + // Return remaining troops to player (ECS component) + let player_id = attack.player_id; + let remaining_troops = attack.get_troops(); + + if let Some(&entity) = entity_map.0.get(&player_id) + && let Ok((mut troops, territory_size)) = players.get_mut(entity) + { + let is_bot = player_id >= human_count.0; + troops.0 = crate::game::entities::add_troops_capped(troops.0, remaining_troops, territory_size.0, is_bot); + } + + // Mark attack for removal + attacks_to_remove.push(attack_key); + } + } + + // Remove completed attacks + for attack_key in attacks_to_remove { + self.remove_attack(attack_key); + } + } + + /// Handle a tile being added to a player's territory + /// + /// Notifies all relevant attacks that territory has changed so they can + /// update their borders and targets. + pub fn handle_territory_add(&mut self, tile: U16Vec2, player_id: u16, territory_manager: &TerritoryManager, rng: &DeterministicRng) { + // Notify all attacks where this player is the attacker + for &attack_key in &self.player_attack_list[player_id as usize] { + self.attacks[attack_key].handle_player_tile_add(tile, territory_manager, rng); + } + + // Notify all attacks where this player is the target + for &attack_key in &self.target_attack_list[player_id as usize] { + self.attacks[attack_key].handle_target_tile_add(tile, territory_manager, rng); + } + } + + /// Add an attack on unclaimed territory + #[allow(clippy::too_many_arguments)] + fn add_unclaimed(&mut self, player_id: u16, troops: f32, target_tile: U16Vec2, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, player_borders: &HashMap>, turn_number: u64, rng: &DeterministicRng) { + let attack = AttackExecutor::new(AttackConfig { player_id, target_id: None, troops, target_tile, border_tiles, territory_manager, player_borders, turn_number }, rng); + + let attack_key = self.attacks.insert(attack); + self.unclaimed_index[player_id as usize] = Some(attack_key); + self.player_attack_list[player_id as usize].insert(attack_key); + } + + /// Add an attack on a player + #[allow(clippy::too_many_arguments)] + fn add_attack(&mut self, player_id: u16, target_id: u16, troops: f32, target_tile: U16Vec2, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, player_borders: &HashMap>, turn_number: u64, rng: &DeterministicRng) { + let attack = AttackExecutor::new(AttackConfig { player_id, target_id: Some(target_id), troops, target_tile, border_tiles, territory_manager, player_borders, turn_number }, rng); + + let attack_key = self.attacks.insert(attack); + self.player_index[player_id as usize][target_id as usize].insert(attack_key); + self.player_attack_list[player_id as usize].insert(attack_key); + self.target_attack_list[target_id as usize].insert(attack_key); + } + + /// Get all attacks involving a specific player (as attacker or target) + /// + /// Returns a list of (attacker_id, target_id, troops, start_turn, is_outgoing) + /// sorted by start_turn descending (most recent first) + pub fn get_attacks_for_player(&self, player_id: u16) -> Vec<(u16, Option, f32, u64, bool)> { + let mut attacks = Vec::new(); + + // Add outgoing attacks (player is attacker) + for &attack_key in &self.player_attack_list[player_id as usize] { + let attack = &self.attacks[attack_key]; + attacks.push(( + attack.player_id, + attack.target_id, + attack.get_troops(), + attack.get_start_turn(), + true, // outgoing + )); + } + + // Add incoming attacks (player is target) + for &attack_key in &self.target_attack_list[player_id as usize] { + let attack = &self.attacks[attack_key]; + attacks.push(( + attack.player_id, + attack.target_id, + attack.get_troops(), + attack.get_start_turn(), + false, // incoming + )); + } + + // Sort by start_turn descending (most recent first) + attacks.sort_by(|a, b| b.3.cmp(&a.3)); + attacks + } + + /// Remove an attack and update all indices + /// + /// With SlotMap, keys remain stable so no index shifting is needed. + /// HashSet provides O(1) removal without element shifting. + fn remove_attack(&mut self, attack_key: AttackKey) { + let attack = &self.attacks[attack_key]; + let player_id = attack.player_id; + let target_id = attack.target_id; + + // Remove from player attack list (O(1)) + self.player_attack_list[player_id as usize].remove(&attack_key); + + if let Some(target_id) = target_id { + // Remove from target attack list (O(1)) + self.target_attack_list[target_id as usize].remove(&attack_key); + + // Remove from player index (O(1)) + self.player_index[player_id as usize][target_id as usize].remove(&attack_key); + } else { + // Remove from unclaimed index + self.unclaimed_index[player_id as usize] = None; + } + + // Remove attack from slot map - no index shifting needed! + self.attacks.remove(attack_key); + } +} diff --git a/crates/borders-core/src/game/combat/calculator.rs b/crates/borders-core/src/game/combat/calculator.rs new file mode 100644 index 0000000..d747466 --- /dev/null +++ b/crates/borders-core/src/game/combat/calculator.rs @@ -0,0 +1,145 @@ +/// Pure combat calculation functions +/// +/// This module contains all combat mathematics extracted from the attack system. +/// All functions are pure (no side effects) and deterministic, making them +/// easy to test, reason about, and modify. +use glam::U16Vec2; + +use crate::game::core::constants::combat::*; +use crate::game::world::TerritoryManager; + +/// Parameters for combat result calculation +pub struct CombatParams<'a> { + pub attacker_troops: f32, + pub attacker_territory_size: usize, + pub defender_troops: Option, + pub defender_territory_size: Option, + pub tile: U16Vec2, + pub territory_manager: &'a TerritoryManager, + pub width: u16, +} + +/// Result of combat calculations for conquering one tile +#[derive(Debug, Clone, Copy)] +pub struct CombatResult { + /// Troops lost by the attacker + pub attacker_loss: f32, + /// Troops lost by the defender + pub defender_loss: f32, + /// How much of the "tiles per tick" budget this conquest consumes + pub tiles_per_tick_used: f32, +} + +/// Sigmoid function for smooth scaling curves +/// +/// Used for empire size balancing to create smooth transitions +/// rather than hard thresholds. +#[inline] +pub fn sigmoid(x: f32, decay_rate: f32, midpoint: f32) -> f32 { + 1.0 / (1.0 + (-(x - midpoint) * decay_rate).exp()) +} + +/// Calculate combat result for conquering one tile +/// +/// This function determines troop losses and conquest cost based on: +/// - Attacker and defender troop counts and empire sizes +/// - Terrain properties (currently plains baseline) +/// - Empire size balancing (prevents snowballing) +/// - Defense structures (placeholder for future implementation) +pub fn calculate_combat_result(params: CombatParams) -> CombatResult { + if let (Some(defender_troops), Some(defender_territory_size)) = (params.defender_troops, params.defender_territory_size) { + // Attacking claimed territory + + // Base terrain values (plains baseline) + let mut mag = BASE_MAG_PLAINS; + let mut speed = BASE_SPEED_PLAINS; + + // Defense post check (placeholder - always false for now) + let has_defense_post = check_defense_post_nearby(params.tile, params.territory_manager); + if has_defense_post { + mag *= DEFENSE_POST_MAG_MULTIPLIER; + speed *= DEFENSE_POST_SPEED_MULTIPLIER; + } + + // Empire size balancing - prevents snowballing + // Large defenders get debuffed, large attackers get penalized + let defense_sig = 1.0 - sigmoid(defender_territory_size as f32, DEFENSE_DEBUFF_DECAY_RATE, DEFENSE_DEBUFF_MIDPOINT); + let large_defender_speed_debuff = LARGE_DEFENDER_BASE_DEBUFF + LARGE_DEFENDER_SCALING * defense_sig; + let large_defender_attack_debuff = LARGE_DEFENDER_BASE_DEBUFF + LARGE_DEFENDER_SCALING * defense_sig; + + let large_attacker_bonus = if params.attacker_territory_size > LARGE_EMPIRE_THRESHOLD as usize { (LARGE_EMPIRE_THRESHOLD as f32 / params.attacker_territory_size as f32).sqrt().powf(LARGE_ATTACKER_POWER_EXPONENT) } else { 1.0 }; + + let large_attacker_speed_bonus = if params.attacker_territory_size > LARGE_EMPIRE_THRESHOLD as usize { (LARGE_EMPIRE_THRESHOLD as f32 / params.attacker_territory_size as f32).powf(LARGE_ATTACKER_SPEED_EXPONENT) } else { 1.0 }; + + // Calculate troop ratio + let troop_ratio = (defender_troops / params.attacker_troops.max(1.0)).clamp(TROOP_RATIO_MIN, TROOP_RATIO_MAX); + + // Final attacker loss + let attacker_loss = troop_ratio * mag * ATTACKER_LOSS_MULTIPLIER * large_defender_attack_debuff * large_attacker_bonus; + + // Defender loss (simple: troops per tile) + let defender_loss = defender_troops / defender_territory_size.max(1) as f32; + + // Tiles per tick cost for this tile + let tiles_per_tick_used = (defender_troops / (TILES_PER_TICK_DIVISOR * params.attacker_troops.max(1.0))).clamp(TILES_PER_TICK_MIN, TILES_PER_TICK_MAX) * speed * large_defender_speed_debuff * large_attacker_speed_bonus; + + CombatResult { attacker_loss, defender_loss, tiles_per_tick_used } + } else { + // Attacking unclaimed territory + CombatResult { attacker_loss: BASE_MAG_PLAINS / UNCLAIMED_ATTACK_LOSS_DIVISOR, defender_loss: 0.0, tiles_per_tick_used: ((UNCLAIMED_BASE_MULTIPLIER * BASE_SPEED_PLAINS.max(MIN_SPEED_PLAINS)) / params.attacker_troops.max(1.0)).clamp(UNCLAIMED_TILES_MIN, UNCLAIMED_TILES_MAX) } + } +} + +/// Calculate tiles conquered per tick based on troop ratio and border size +/// +/// This determines how fast an attack progresses. It's based on: +/// - The attacker's troop advantage (or disadvantage) +/// - The size of the attack border +/// - Random variation for organic-looking expansion +pub fn calculate_tiles_per_tick(attacker_troops: f32, defender_troops: Option, border_size: f32) -> f32 { + if let Some(defender_troops) = defender_troops { + // Dynamic based on troop ratio + let ratio = ((ATTACK_RATIO_MULTIPLIER * attacker_troops) / defender_troops.max(1.0)) * ATTACK_RATIO_SCALE; + let clamped_ratio = ratio.clamp(ATTACK_RATIO_MIN, ATTACK_RATIO_MAX); + clamped_ratio * border_size * CLAIMED_TILES_PER_TICK_MULTIPLIER + } else { + // Fixed rate for unclaimed territory + border_size * UNCLAIMED_TILES_PER_TICK_MULTIPLIER + } +} + +/// Check if defender has a defense post nearby (placeholder) +/// +/// This will be implemented when defense structures are added to the game. +/// For now, always returns false. +fn check_defense_post_nearby(_tile: U16Vec2, _territory_manager: &TerritoryManager) -> bool { + // Placeholder for future defense post implementation + false +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sigmoid_midpoint() { + let result = sigmoid(DEFENSE_DEBUFF_MIDPOINT, DEFENSE_DEBUFF_DECAY_RATE, DEFENSE_DEBUFF_MIDPOINT); + assert!((result - 0.5).abs() < 0.01, "Sigmoid should be ~0.5 at midpoint"); + } + + #[test] + fn test_clamp() { + assert_eq!(5.0_f32.clamp(0.0, 10.0), 5.0); + assert_eq!((-1.0_f32).clamp(0.0, 10.0), 0.0); + assert_eq!(15.0_f32.clamp(0.0, 10.0), 10.0); + } + + #[test] + fn test_unclaimed_attack_fixed_losses() { + // Unclaimed territory should have fixed attacker loss + let result = CombatResult { attacker_loss: BASE_MAG_PLAINS / 5.0, defender_loss: 0.0, tiles_per_tick_used: 10.0 }; + + assert_eq!(result.attacker_loss, 16.0); + assert_eq!(result.defender_loss, 0.0); + } +} diff --git a/crates/borders-core/src/game/combat/executor.rs b/crates/borders-core/src/game/combat/executor.rs new file mode 100644 index 0000000..2afb104 --- /dev/null +++ b/crates/borders-core/src/game/combat/executor.rs @@ -0,0 +1,377 @@ +/// Attack execution logic +/// +/// This module contains the `AttackExecutor` which manages the progression +/// of a single attack over multiple turns. It handles tile prioritization, +/// border expansion, and conquest mechanics. +use std::collections::{BinaryHeap, HashMap, HashSet}; + +use glam::U16Vec2; +use rand::Rng; + +use super::calculator::{CombatParams, calculate_combat_result, calculate_tiles_per_tick}; +use crate::game::TileOwnership; +use crate::game::core::constants::combat::*; +use crate::game::core::rng::DeterministicRng; +use crate::game::core::utils::neighbors; +use crate::game::entities::{PlayerEntityMap, TerritorySize, Troops}; +use crate::game::world::TerritoryManager; +use bevy_ecs::prelude::*; + +/// Priority queue entry for tile conquest +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct TilePriority { + tile: U16Vec2, + priority: i64, // Lower value = higher priority (conquered sooner) +} + +impl PartialOrd for TilePriority { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TilePriority { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + other.priority.cmp(&self.priority).then_with(|| self.tile.x.cmp(&other.tile.x).then_with(|| self.tile.y.cmp(&other.tile.y))) + } +} + +/// Configuration for creating an AttackExecutor +pub struct AttackConfig<'a> { + pub player_id: u16, + pub target_id: Option, + pub troops: f32, + pub target_tile: U16Vec2, + pub border_tiles: Option<&'a HashSet>, + pub territory_manager: &'a TerritoryManager, + pub player_borders: &'a HashMap>, + pub turn_number: u64, +} + +/// Executes a single ongoing attack (conquering tiles over time) +/// +/// An attack progresses over multiple turns, conquering tiles based on: +/// - Available troops +/// - Troop ratio vs defender +/// - Border size and connectivity +/// - Combat formulas from the calculator module +/// +/// The executor maintains a priority queue of tiles to conquer and updates +/// borders as it progresses. +pub struct AttackExecutor { + pub player_id: u16, + pub target_id: Option, + troops: f32, + /// Active conquest frontier - tiles being evaluated/conquered by this attack. + /// Distinct from player BorderTiles: dynamically shrinks as tiles are conquered + /// and expands as new neighbors become targets. + conquest_frontier: HashSet, + priority_queue: BinaryHeap, + start_turn: u64, + current_turn: u64, + tiles_conquered: usize, // Counter for each tile conquered (for priority calculation) +} + +impl AttackExecutor { + /// Create a new attack executor + pub fn new(config: AttackConfig, rng: &DeterministicRng) -> Self { + let mut executor = Self { player_id: config.player_id, target_id: config.target_id, troops: config.troops, conquest_frontier: HashSet::new(), priority_queue: BinaryHeap::new(), start_turn: config.turn_number, current_turn: config.turn_number, tiles_conquered: 0 }; + + executor.initialize_border(config.border_tiles, config.territory_manager, config.player_borders, rng); + + executor + } + + /// Modify the amount of troops in the attack + pub fn modify_troops(&mut self, amount: f32) { + self.troops += amount; + } + + /// Add new border tiles to the attack, allowing expansion from multiple fronts + /// + /// This enables multi-region expansion when attacking the same target from different areas + pub fn add_borders(&mut self, new_border_tiles: &HashSet, territory_manager: &TerritoryManager, rng: &DeterministicRng) { + // Add neighbors from each new border tile + for &tile in new_border_tiles { + for neighbor in neighbors(tile, territory_manager.size()) { + if self.is_valid_target(neighbor, territory_manager) && !self.conquest_frontier.contains(&neighbor) { + self.add_tile_to_border(neighbor, territory_manager, rng); + } + } + } + } + + /// Oppose an attack (counter-attack) + /// + /// Returns true if the attack continues, false if it was defeated + pub fn oppose(&mut self, troop_count: f32) -> bool { + if self.troops > troop_count { + self.troops -= troop_count; + true + } else { + false + } + } + + /// Get the amount of troops in the attack + pub fn get_troops(&self) -> f32 { + self.troops.max(0.0).floor() + } + + /// Get the turn this attack started + pub fn get_start_turn(&self) -> u64 { + self.start_turn + } + + /// Tick the attack executor + /// + /// Returns true if the attack continues, false if it's finished + pub fn tick(&mut self, entity_map: &PlayerEntityMap, players: &mut Query<(&mut Troops, &mut TerritorySize)>, territory_manager: &mut TerritoryManager, player_borders: &HashMap>, rng: &DeterministicRng) -> bool { + let _guard = tracing::trace_span!("attack_tick", player_id = self.player_id).entered(); + + self.current_turn += 1; + + // Calculate how many tiles to conquer this tick + let mut tiles_per_tick = self.calculate_tiles_per_tick(entity_map, players, rng); + + // Track if we've already refreshed this tick to prevent infinite refresh loops + let mut has_refreshed = false; + + // Process tiles from priority queue + while tiles_per_tick > 0.0 { + if self.troops < 1.0 { + return false; + } + + if self.priority_queue.is_empty() { + // If we already refreshed this tick, stop to prevent infinite loop + if has_refreshed { + return false; + } + + // Remember border size before refresh + let border_size_before = self.conquest_frontier.len(); + + // Refresh border tiles one last time before giving up + self.refresh_border(player_borders, territory_manager, rng); + has_refreshed = true; + + // If refresh found no new tiles, attack is finished + if self.conquest_frontier.len() == border_size_before { + return false; + } + + // If still empty after refresh (all tiles invalid), attack is finished + if self.priority_queue.is_empty() { + return false; + } + } + + let tile_priority = self.priority_queue.pop().unwrap(); + let tile = tile_priority.tile; + self.conquest_frontier.remove(&tile); + + // Check connectivity and validity + let on_border = Self::check_borders_tile(tile, self.player_id, territory_manager); + let tile_valid = self.is_valid_target(tile, territory_manager); + + // Skip if either check fails + if !tile_valid || !on_border { + continue; + } + + // Add neighbors BEFORE conquering (critical for correct expansion) + self.add_neighbors_to_border(tile, territory_manager, rng); + + // Query attacker stats from ECS + let attacker_entity = entity_map.0.get(&self.player_id); + if attacker_entity.is_none() { + return false; + } + let (attacker_troops_comp, attacker_territory) = match players.get(*attacker_entity.unwrap()) { + Ok(comps) => comps, + Err(_) => return false, + }; + let attacker_troops = attacker_troops_comp.0; + let attacker_territory_size = attacker_territory.0; + + // Query defender stats from ECS if attacking a player + let (defender_troops, defender_territory_size) = if let Some(target_id) = self.target_id { if let Some(&defender_entity) = entity_map.0.get(&target_id) { if let Ok((troops, territory)) = players.get(defender_entity) { (Some(troops.0), Some(territory.0)) } else { (None, None) } } else { (None, None) } } else { (None, None) }; + + // Calculate losses for this tile + let combat_result = { calculate_combat_result(CombatParams { attacker_troops, attacker_territory_size: attacker_territory_size as usize, defender_troops, defender_territory_size: defender_territory_size.map(|s| s as usize), tile, territory_manager, width: territory_manager.width() }) }; + + // Check if we still have enough troops to conquer this tile + if self.troops < combat_result.attacker_loss { + return false; + } + + // Apply troop losses + self.troops -= combat_result.attacker_loss; + if let Some(target_id) = self.target_id + && let Some(&defender_entity) = entity_map.0.get(&target_id) + && let Ok((mut troops, _)) = players.get_mut(defender_entity) + { + troops.0 = (troops.0 - combat_result.defender_loss).max(0.0); + } + + // Conquer the tile + let previous_owner = territory_manager.conquer(tile, self.player_id); + + // Update player territory sizes + if let Some(nation_id) = TileOwnership::from_u16(previous_owner).nation_id() + && let Some(&entity) = entity_map.0.get(&nation_id) + && let Ok((_, mut territory_size)) = players.get_mut(entity) + { + territory_size.0 = territory_size.0.saturating_sub(1); + } + if let Some(&entity) = entity_map.0.get(&self.player_id) + && let Ok((_, mut territory_size)) = players.get_mut(entity) + { + territory_size.0 += 1; + } + + // Increment tiles conquered counter (used for priority calculation) + self.tiles_conquered += 1; + + // Decrement tiles per tick counter + tiles_per_tick -= combat_result.tiles_per_tick_used; + } + + // Check if attack should continue + !self.priority_queue.is_empty() && self.troops >= 1.0 + } + + /// Calculate tiles conquered per tick based on troop ratio and border size + fn calculate_tiles_per_tick(&mut self, entity_map: &PlayerEntityMap, players: &Query<(&mut Troops, &mut TerritorySize)>, rng: &DeterministicRng) -> f32 { + // Add random 0-4 to border size + // This introduces natural variation in expansion speed + let mut context_rng = rng.for_context(self.player_id as u64); + let random_border_adjustment = context_rng.random_range(0..BORDER_RANDOM_ADJUSTMENT_MAX) as f32; + let border_size = self.conquest_frontier.len() as f32 + random_border_adjustment; + + // Query defender troops if attacking a player + let defender_troops = if let Some(target_id) = self.target_id { entity_map.0.get(&target_id).and_then(|&entity| players.get(entity).ok()).map(|(troops, _)| troops.0) } else { None }; + + calculate_tiles_per_tick(self.troops, defender_troops, border_size) + } + + /// Check if a tile is a valid target for this attack + fn is_valid_target(&self, tile: U16Vec2, territory_manager: &TerritoryManager) -> bool { + if let Some(target_id) = self.target_id { territory_manager.is_owner(tile, target_id) } else { !territory_manager.has_owner(tile) && !territory_manager.is_water(tile) } + } + + /// Add a tile to the border with proper priority calculation + fn add_tile_to_border(&mut self, tile: U16Vec2, territory_manager: &TerritoryManager, rng: &DeterministicRng) { + self.conquest_frontier.insert(tile); + let priority = self.calculate_tile_priority(tile, territory_manager, rng); + self.priority_queue.push(TilePriority { tile, priority }); + } + + /// Initialize border tiles from player's existing borders + fn initialize_border(&mut self, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, player_borders: &HashMap>, rng: &DeterministicRng) { + self.initialize_border_internal(border_tiles, territory_manager, player_borders, rng, false); + } + + /// Refresh the attack border by re-scanning all player border tiles + /// + /// This gives the attack one last chance to find conquerable tiles before ending + fn refresh_border(&mut self, player_borders: &HashMap>, territory_manager: &TerritoryManager, rng: &DeterministicRng) { + self.initialize_border_internal(None, territory_manager, player_borders, rng, true); + } + + /// Internal method to initialize or refresh border tiles + fn initialize_border_internal(&mut self, border_tiles: Option<&HashSet>, territory_manager: &TerritoryManager, player_borders: &HashMap>, rng: &DeterministicRng, clear_first: bool) { + if clear_first { + self.priority_queue.clear(); + self.conquest_frontier.clear(); + } + + // Get borders or use empty set as fallback (needs lifetime handling) + let empty_borders = HashSet::new(); + let borders = border_tiles.or_else(|| player_borders.get(&self.player_id).copied()).unwrap_or(&empty_borders); + + let border_count = borders.len(); + let _refresh_guard; + let _init_guard; + if clear_first { + _refresh_guard = tracing::trace_span!("refresh_attack_border", border_count).entered(); + } else { + _init_guard = tracing::trace_span!("initialize_attack_border", border_count).entered(); + tracing::debug!( + "Initializing attack border for player {}: {} border tiles from {}", + self.player_id, + border_count, + if border_tiles.is_some() { + "provided borders" + } else if player_borders.contains_key(&self.player_id) { + "player_borders cache" + } else { + "empty (NO BORDERS FOUND!)" + } + ); + } + + // Find all target tiles adjacent to our borders + for &tile in borders { + for neighbor in neighbors(tile, territory_manager.size()) { + if self.is_valid_target(neighbor, territory_manager) && !self.conquest_frontier.contains(&neighbor) { + self.add_tile_to_border(neighbor, territory_manager, rng); + } + } + } + } + + /// Add neighbors of a newly conquered tile to the border + fn add_neighbors_to_border(&mut self, tile: U16Vec2, territory_manager: &TerritoryManager, rng: &DeterministicRng) { + for neighbor in neighbors(tile, territory_manager.size()) { + if self.is_valid_target(neighbor, territory_manager) && !self.conquest_frontier.contains(&neighbor) { + self.add_tile_to_border(neighbor, territory_manager, rng); + } + } + } + + /// Calculate priority for a tile (lower = conquered sooner) + /// + /// Uses tiles_conquered counter to ensure wave-like expansion + fn calculate_tile_priority(&self, tile: U16Vec2, territory_manager: &TerritoryManager, rng: &DeterministicRng) -> i64 { + // Count how many neighbors are owned by attacker + let num_owned_by_attacker = neighbors(tile, territory_manager.size()).filter(|&neighbor| territory_manager.is_owner(neighbor, self.player_id)).count(); + + // Terrain magnitude (placeholder - always 1.0 for plains) + let terrain_mag = 1.0; + + // Random factor (0-7) + let mut tile_rng = rng.for_tile(tile); + let random_factor = tile_rng.random_range(0..TILE_PRIORITY_RANDOM_MAX); + + // Priority calculation (lower = higher priority, conquered sooner) + // Base calculation: tiles surrounded by more attacker neighbors get LOWER modifier values + // Adding tiles_conquered ensures tiles discovered earlier get lower priority values + // This creates wave-like expansion: older tiles (lower priority) conquered before newer tiles (higher priority) + let base = (random_factor + 10) as f32; + let modifier = TILE_PRIORITY_BASE - (num_owned_by_attacker as f32 * TILE_PRIORITY_NEIGHBOR_PENALTY) + (terrain_mag / 2.0); + (base * modifier) as i64 + self.tiles_conquered as i64 + } + + /// Handle the addition of a tile to the player's territory + pub fn handle_player_tile_add(&mut self, tile: U16Vec2, territory_manager: &TerritoryManager, rng: &DeterministicRng) { + // When player gains a tile, check its neighbors for new targets + self.add_neighbors_to_border(tile, territory_manager, rng); + } + + /// Handle the addition of a tile to the target's territory + pub fn handle_target_tile_add(&mut self, tile: U16Vec2, territory_manager: &TerritoryManager, rng: &DeterministicRng) { + // If target gains a tile that borders our territory, add it to attack + if Self::check_borders_tile(tile, self.player_id, territory_manager) && !self.conquest_frontier.contains(&tile) { + self.conquest_frontier.insert(tile); + let priority = self.calculate_tile_priority(tile, territory_manager, rng); + self.priority_queue.push(TilePriority { tile, priority }); + } + } + + /// Check if a tile borders the player's territory + fn check_borders_tile(tile: U16Vec2, player_id: u16, territory_manager: &TerritoryManager) -> bool { + neighbors(tile, territory_manager.size()).any(|neighbor| territory_manager.is_owner(neighbor, player_id)) + } +} diff --git a/crates/borders-core/src/game/combat/mod.rs b/crates/borders-core/src/game/combat/mod.rs new file mode 100644 index 0000000..1bf65f9 --- /dev/null +++ b/crates/borders-core/src/game/combat/mod.rs @@ -0,0 +1,7 @@ +pub mod active; +pub mod calculator; +pub mod executor; + +pub use active::*; +pub use calculator::*; +pub use executor::*; diff --git a/crates/borders-core/src/game/core/action.rs b/crates/borders-core/src/game/core/action.rs new file mode 100644 index 0000000..73b1fe0 --- /dev/null +++ b/crates/borders-core/src/game/core/action.rs @@ -0,0 +1,47 @@ +//! Game action system +//! +//! This module defines the core action types that can be performed in the game. +//! Actions represent discrete game events that can be initiated by both human players +//! and AI bots. They are processed deterministically during turn execution. + +use serde::{Deserialize, Serialize}; + +use crate::game::core::utils::u16vec2_serde; + +/// Core game action type +/// +/// This enum represents all possible actions that can be performed in the game. +/// Unlike `Intent`, which is a network-layer wrapper, `GameAction` is the actual +/// game-level operation. +/// +/// Actions can originate from: +/// - Human players (via input systems → intents → network) +/// - AI bots (calculated deterministically during turn execution) +/// +/// Note: Spawning is handled separately via Turn(0) and direct spawn manager updates, +/// not through the action system. +#[derive(Debug, Clone, Serialize, Deserialize, bincode::Encode, bincode::Decode)] +pub enum GameAction { + /// Attack a target tile with a percentage of the player's total troops + Attack { + player_id: u16, + #[serde(with = "u16vec2_serde")] + #[bincode(with_serde)] + target_tile: glam::U16Vec2, + troops_ratio: f32, + }, + /// Launch a transport ship to attack across water + LaunchShip { + player_id: u16, + #[serde(with = "u16vec2_serde")] + #[bincode(with_serde)] + target_tile: glam::U16Vec2, + /// Troops as a percentage (0-100) to avoid float precision issues + troops_percent: u32, + }, + // Future action types: + // BuildStructure { player_id: u16, tile_index: U16Vec2, structure_type: StructureType }, + // LaunchNuke { player_id: u16, target_tile: U16Vec2 }, + // RequestAlliance { player_id: u16, target_player: u16 }, + // DeclareWar { player_id: u16, target_player: u16 }, +} diff --git a/crates/borders-core/src/game/core/constants.rs b/crates/borders-core/src/game/core/constants.rs new file mode 100644 index 0000000..ad5b03f --- /dev/null +++ b/crates/borders-core/src/game/core/constants.rs @@ -0,0 +1,261 @@ +/// Game constants organized by domain +/// +/// This module centralizes all game balance constants that were previously +/// scattered across multiple files. Constants are grouped by gameplay domain +/// for easy discovery and tuning. +pub mod game { + /// Game tick interval in milliseconds (10 TPS = 100ms per turn) + pub const TICK_INTERVAL: u64 = 100; + + /// Number of bot players + pub const BOT_COUNT: usize = 500; +} + +pub mod combat { + /// Empire size balancing - prevents snowballing by large empires + /// Defense effectiveness decreases as empire grows beyond this threshold + pub const DEFENSE_DEBUFF_MIDPOINT: f32 = 150_000.0; + + /// Rate of defense effectiveness decay for large empires + /// Uses natural log decay for smooth scaling + pub const DEFENSE_DEBUFF_DECAY_RATE: f32 = std::f32::consts::LN_2 / 50_000.0; + + /// Base terrain magnitude cost for plains (baseline terrain) + /// Determines troop losses when conquering a tile + pub const BASE_MAG_PLAINS: f32 = 80.0; + + /// Base terrain speed for plains (baseline terrain) + /// Affects how many tiles can be conquered per tick + pub const BASE_SPEED_PLAINS: f32 = 16.5; + + /// Maximum random adjustment to border size when calculating expansion speed + /// Introduces natural variation in attack progression (0-4 range) + pub const BORDER_RANDOM_ADJUSTMENT_MAX: u32 = 5; + + /// Multiplier for tiles conquered per tick when attacking unclaimed territory + pub const UNCLAIMED_TILES_PER_TICK_MULTIPLIER: f32 = 2.0; + + /// Multiplier for tiles conquered per tick when attacking claimed territory + pub const CLAIMED_TILES_PER_TICK_MULTIPLIER: f32 = 3.0; + + /// Large empire threshold for attack penalties (>100k tiles) + pub const LARGE_EMPIRE_THRESHOLD: u32 = 100_000; + + /// Random factor range for tile priority calculation (0-7) + pub const TILE_PRIORITY_RANDOM_MAX: u32 = 8; + + /// Defense post magnitude multiplier (when implemented) + pub const DEFENSE_POST_MAG_MULTIPLIER: f32 = 5.0; + + /// Defense post speed multiplier (when implemented) + pub const DEFENSE_POST_SPEED_MULTIPLIER: f32 = 3.0; + + /// Base defense debuff for large defenders (70%) + pub const LARGE_DEFENDER_BASE_DEBUFF: f32 = 0.7; + + /// Scaling factor for large defender sigmoid (30%) + pub const LARGE_DEFENDER_SCALING: f32 = 0.3; + + /// Power exponent for large attacker bonus calculation + pub const LARGE_ATTACKER_POWER_EXPONENT: f32 = 0.7; + + /// Speed exponent for large attacker penalty calculation + pub const LARGE_ATTACKER_SPEED_EXPONENT: f32 = 0.6; + + /// Minimum troop ratio for combat calculations + pub const TROOP_RATIO_MIN: f32 = 0.6; + + /// Maximum troop ratio for combat calculations + pub const TROOP_RATIO_MAX: f32 = 2.0; + + /// Multiplier for attacker loss calculations + pub const ATTACKER_LOSS_MULTIPLIER: f32 = 0.8; + + /// Divisor for tiles per tick calculation + pub const TILES_PER_TICK_DIVISOR: f32 = 5.0; + + /// Minimum tiles per tick cost + pub const TILES_PER_TICK_MIN: f32 = 0.2; + + /// Maximum tiles per tick cost + pub const TILES_PER_TICK_MAX: f32 = 1.5; + + /// Divisor for unclaimed territory attack losses + pub const UNCLAIMED_ATTACK_LOSS_DIVISOR: f32 = 5.0; + + /// Base multiplier for unclaimed territory conquest speed + pub const UNCLAIMED_BASE_MULTIPLIER: f32 = 2000.0; + + /// Minimum speed value for plains terrain + pub const MIN_SPEED_PLAINS: f32 = 10.0; + + /// Minimum tiles per tick for unclaimed territory + pub const UNCLAIMED_TILES_MIN: f32 = 5.0; + + /// Maximum tiles per tick for unclaimed territory + pub const UNCLAIMED_TILES_MAX: f32 = 100.0; + + /// Multiplier for attack ratio calculation + pub const ATTACK_RATIO_MULTIPLIER: f32 = 5.0; + + /// Scale factor for attack ratio + pub const ATTACK_RATIO_SCALE: f32 = 2.0; + + /// Minimum attack ratio for dynamic calculation + pub const ATTACK_RATIO_MIN: f32 = 0.01; + + /// Maximum attack ratio for dynamic calculation + pub const ATTACK_RATIO_MAX: f32 = 0.5; + + /// Base priority value for tile conquest + pub const TILE_PRIORITY_BASE: f32 = 1.0; + + /// Priority penalty per owned neighbor tile + pub const TILE_PRIORITY_NEIGHBOR_PENALTY: f32 = 0.5; +} + +pub mod player { + /// Multiplier for max troops calculation + pub const MAX_TROOPS_MULTIPLIER: f32 = 2.0; + + /// Power exponent for max troops based on territory size + pub const MAX_TROOPS_POWER: f32 = 0.6; + + /// Scale factor for max troops calculation + pub const MAX_TROOPS_SCALE: f32 = 1000.0; + + /// Base max troops value + pub const MAX_TROOPS_BASE: f32 = 50_000.0; + + /// Bots get 33% of human max troops + pub const BOT_MAX_TROOPS_MULTIPLIER: f32 = 0.33; + + /// Base income per tick + pub const BASE_INCOME: f32 = 10.0; + + /// Power exponent for income calculation + pub const INCOME_POWER: f32 = 0.73; + + /// Divisor for income calculation + pub const INCOME_DIVISOR: f32 = 4.0; + + /// Bots get 60% of human income + pub const BOT_INCOME_MULTIPLIER: f32 = 0.6; + + /// Initial troops for all players at spawn + pub const INITIAL_TROOPS: f32 = 2500.0; +} + +pub mod bot { + /// Maximum initial cooldown for bot actions (0-9 ticks) + pub const INITIAL_COOLDOWN_MAX: u64 = 10; + + /// Minimum cooldown between bot actions (ticks) + pub const ACTION_COOLDOWN_MIN: u64 = 3; + + /// Maximum cooldown between bot actions (ticks) + pub const ACTION_COOLDOWN_MAX: u64 = 15; + + /// Probability that bot chooses expansion over attack (60%) + pub const EXPAND_PROBABILITY: f32 = 0.6; + + /// Minimum troop percentage for wilderness expansion (10%) + pub const EXPAND_TROOPS_MIN: f32 = 0.1; + + /// Maximum troop percentage for wilderness expansion (30%) + pub const EXPAND_TROOPS_MAX: f32 = 0.3; + + /// Minimum troop percentage for player attacks (20%) + pub const ATTACK_TROOPS_MIN: f32 = 0.2; + + /// Maximum troop percentage for player attacks (50%) + pub const ATTACK_TROOPS_MAX: f32 = 0.5; + + /// Minimum distance between spawn points (in tiles) + pub const MIN_SPAWN_DISTANCE: f32 = 70.0; + + /// Absolute minimum spawn distance for fallback + pub const ABSOLUTE_MIN_DISTANCE: f32 = 5.0; + + /// Distance reduction factor per adaptive wave (15% reduction) + pub const DISTANCE_REDUCTION_FACTOR: f32 = 0.85; + + /// Number of random spawn placement attempts + pub const SPAWN_RANDOM_ATTEMPTS: usize = 1000; + + /// Maximum attempts for grid-guided spawn placement + pub const SPAWN_GRID_MAX_ATTEMPTS: usize = 200; + + /// Maximum attempts for fallback spawn placement + pub const SPAWN_FALLBACK_ATTEMPTS: usize = 10000; + + /// Stride factor for grid-guided spawn placement (80% of current distance) + pub const SPAWN_GRID_STRIDE_FACTOR: f32 = 0.8; + + /// Maximum border tiles sampled for bot decision making + pub const MAX_BORDER_SAMPLES: usize = 20; +} + +pub mod colors { + /// Minimum hue value for color generation (degrees) + pub const HUE_MIN: f32 = 0.0; + + /// Maximum hue value for color generation (degrees) + pub const HUE_MAX: f32 = 360.0; + + /// Golden angle for visually distinct color distribution (degrees) + pub const GOLDEN_ANGLE: f32 = 137.5; + + /// Minimum saturation for player colors + pub const SATURATION_MIN: f32 = 0.75; + + /// Maximum saturation for player colors + pub const SATURATION_MAX: f32 = 0.95; + + /// Minimum lightness for player colors + pub const LIGHTNESS_MIN: f32 = 0.35; + + /// Maximum lightness for player colors + pub const LIGHTNESS_MAX: f32 = 0.65; +} + +pub mod input { + /// Default attack ratio when game starts (50%) + pub const DEFAULT_ATTACK_RATIO: f32 = 0.5; + + /// Step size for attack ratio adjustment (10%) + pub const ATTACK_RATIO_STEP: f32 = 0.1; + + /// Minimum attack ratio (10%) + pub const ATTACK_RATIO_MIN: f32 = 0.1; + + /// Maximum attack ratio (100%) + pub const ATTACK_RATIO_MAX: f32 = 1.0; +} + +pub mod ships { + /// Maximum ships per player + pub const MAX_SHIPS_PER_PLAYER: usize = 5; + + /// Ticks required to move one tile (1 = fast speed) + pub const TICKS_PER_TILE: u32 = 1; + + /// Maximum path length for ship pathfinding + pub const MAX_PATH_LENGTH: usize = 1_000_000; + + /// Percentage of troops carried by ship (20%) + pub const TROOP_PERCENT: f32 = 0.20; +} + +pub mod outcome { + /// Win threshold - percentage of map needed to win (80%) + pub const WIN_THRESHOLD: f32 = 0.80; +} + +pub mod spawning { + /// Radius of tiles claimed around spawn point (creates 5x5 square) + pub const SPAWN_RADIUS: i16 = 2; + + /// Spawn timeout duration in seconds + pub const SPAWN_TIMEOUT_SECS: f32 = 2.0; +} diff --git a/crates/borders-core/src/game/core/instance.rs b/crates/borders-core/src/game/core/instance.rs new file mode 100644 index 0000000..1693f69 --- /dev/null +++ b/crates/borders-core/src/game/core/instance.rs @@ -0,0 +1,7 @@ +/// Troop count specification for attacks +pub enum TroopCount { + /// Use a ratio of the player's current troops (0.0-1.0) + Ratio(f32), + /// Use an absolute troop count + Absolute(u32), +} diff --git a/crates/borders-core/src/game/core/lifecycle.rs b/crates/borders-core/src/game/core/lifecycle.rs new file mode 100644 index 0000000..8e92aef --- /dev/null +++ b/crates/borders-core/src/game/core/lifecycle.rs @@ -0,0 +1,201 @@ +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; + +use bevy_ecs::prelude::*; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; +use tracing::{debug, info}; + +use crate::TerrainData; +use crate::game::core::constants::colors::*; +use crate::game::core::constants::game::BOT_COUNT; +use crate::game::core::constants::player::INITIAL_TROOPS; +use crate::game::core::constants::spawning::SPAWN_TIMEOUT_SECS; +use crate::game::core::rng::DeterministicRng; +use crate::game::{ActiveAttacks, BotManager, CoastalTiles, HSLColor, LocalPlayerContext, SpawnManager, SpawnPhase, SpawnTimeout, TerritoryManager}; +use crate::networking::{GameView, LocalTurnServerHandle, PlayerView, TurnGenerator, TurnReceiver}; +use flume::Receiver; + +/// Parameters needed to initialize a new game +pub struct GameInitParams { + pub map_width: u16, + pub map_height: u16, + pub conquerable_tiles: Vec, + pub client_player_id: u16, + pub intent_rx: Receiver, + pub terrain_data: Arc, +} + +/// Initialize all game resources when starting a new game +/// This should be called by the StartGame command handler +pub fn initialize_game_resources(commands: &mut Commands, params: GameInitParams) { + let _guard = tracing::trace_span!("game_initialization", map_width = params.map_width, map_height = params.map_height).entered(); + + info!("Initializing game resources (map: {}x{}, player: {})", params.map_width, params.map_height, params.client_player_id); + + // Initialize territory manager + let mut territory_manager = TerritoryManager::new(params.map_width, params.map_height); + territory_manager.reset(params.map_width, params.map_height, ¶ms.conquerable_tiles); + debug!("Territory manager initialized with {} tiles", params.conquerable_tiles.len()); + + // Initialize active attacks + let mut active_attacks = ActiveAttacks::new(); + active_attacks.init(1 + BOT_COUNT); + + // Initialize bot manager (1 human player + BOT_COUNT bots) + let bot_manager = BotManager::new(BOT_COUNT, 1); + debug!("BotManager initialized with {} bots", BOT_COUNT); + + // Use a fixed seed for deterministic bot behavior and color generation + // In multiplayer, this should come from the server + let rng_seed = 0xDEADBEEF; + + // Create RNG for deterministic color generation + let mut rng = StdRng::seed_from_u64(rng_seed); + + // Generate player metadata: 1 human + BOT_COUNT bots + // Player IDs start at 0 (human), then 1, 2, 3... for bots + let mut player_metadata = Vec::new(); + + // Generate random hue offset for color spread + let hue_offset = rng.random_range(HUE_MIN..HUE_MAX); + + // All players (including human) get deterministically generated colors + for i in 0..=BOT_COUNT { + let is_human = i == 0; + let player_id = i as u16; + + // Use golden angle distribution with random offset for visually distinct colors + let hue = (player_id as f32 * GOLDEN_ANGLE + hue_offset) % HUE_MAX; + let saturation = rng.random_range(SATURATION_MIN..=SATURATION_MAX); + let lightness = rng.random_range(LIGHTNESS_MIN..=LIGHTNESS_MAX); + let color = HSLColor::new(hue, saturation, lightness); + + let name = if is_human { "Player".to_string() } else { format!("Bot {}", i) }; + + player_metadata.push((player_id, name, color)); + } + + debug!("Player metadata generated for {} players (human: 0, bots: {})", 1 + BOT_COUNT, BOT_COUNT); + + // Calculate initial bot spawn positions (first pass) + // These will be shown to the player, but not applied to game state yet + let initial_bot_spawns = bot_manager.calculate_initial_spawns(&territory_manager, rng_seed); + + debug!("Calculated {} initial bot spawn positions (requested: {})", initial_bot_spawns.len(), BOT_COUNT); + + if initial_bot_spawns.len() < BOT_COUNT { + tracing::warn!("Only {} of {} bots were able to spawn - map may be too small or bot count too high", initial_bot_spawns.len(), BOT_COUNT); + } + + // Create SpawnManager to track spawn positions during spawn phase + let spawn_manager = SpawnManager::new(initial_bot_spawns.clone(), rng_seed); + commands.insert_resource(spawn_manager); + + // Initialize GameView with initial game state + use std::sync::Arc; + + let total_land_tiles = territory_manager.as_slice().iter().filter(|ownership| !ownership.is_water()).count() as u32; + + // Spawn player entities with ECS components + // This ensures entities exist from the start for update_player_borders_system + let bot_player_ids: Vec = bot_manager.bot_player_ids().to_vec(); + + // Create entity map for O(1) player_id -> Entity lookups + let mut entity_map = crate::game::PlayerEntityMap::default(); + + // Initial troops and territory for each player + let initial_troops = INITIAL_TROOPS; + let initial_territory_size = 0; + + for (player_id, name, color) in &player_metadata { + let is_bot = bot_player_ids.contains(player_id); + + let entity = if is_bot { commands.spawn((crate::game::BotPlayer, crate::game::PlayerId(*player_id), crate::game::PlayerName(name.clone()), crate::game::PlayerColor(*color), crate::game::BorderTiles::default(), crate::game::Troops(initial_troops), crate::game::TerritorySize(initial_territory_size), crate::game::ships::ShipCount::default())).id() } else { commands.spawn((crate::game::PlayerId(*player_id), crate::game::PlayerName(name.clone()), crate::game::PlayerColor(*color), crate::game::BorderTiles::default(), crate::game::Troops(initial_troops), crate::game::TerritorySize(initial_territory_size), crate::game::ships::ShipCount::default())).id() }; + + entity_map.0.insert(*player_id, entity); + } + + debug!("Player entities spawned with ECS components ({} total)", 1 + BOT_COUNT); + + // Build initial GameView by reading from the ECS entities we just created + let game_view = GameView { + size: glam::U16Vec2::new(params.map_width, params.map_height), + territories: Arc::from(territory_manager.as_u16_slice()), + turn_number: 0, + total_land_tiles, + changed_tiles: Vec::new(), // Empty on initialization + players: player_metadata.iter().map(|(player_id, name, color)| PlayerView { id: *player_id, color: color.to_rgba(), name: name.clone(), tile_count: initial_territory_size, troops: initial_troops as u32, is_alive: true }).collect(), + ships: Vec::new(), // No ships at initialization + }; + + // Compute coastal tiles once + let map_size = glam::U16Vec2::new(params.map_width, params.map_height); + let coastal_tiles = CoastalTiles::compute(¶ms.terrain_data, map_size); + debug!("Computed {} coastal tiles", coastal_tiles.len()); + + // Insert all individual game resources + commands.insert_resource(entity_map); + commands.insert_resource(crate::game::ClientPlayerId(params.client_player_id)); + commands.insert_resource(crate::game::HumanPlayerCount(1)); + commands.insert_resource(crate::game::ships::ShipIdCounter::new()); + commands.insert_resource(territory_manager); + commands.insert_resource(active_attacks); + commands.insert_resource(bot_manager); + commands.insert_resource(params.terrain_data.as_ref().clone()); + commands.insert_resource(DeterministicRng::new(rng_seed)); + commands.insert_resource(coastal_tiles); + commands.insert_resource(game_view); + + // Initialize local player context + commands.insert_resource(LocalPlayerContext::new(0)); // Human player is ID 0 + debug!("LocalPlayerContext created for player 0 (human)"); + + // Initialize spawn timeout + commands.insert_resource(SpawnTimeout::new(SPAWN_TIMEOUT_SECS)); + debug!("SpawnTimeout initialized ({} seconds)", SPAWN_TIMEOUT_SECS); + + // Initialize turn generation resources + let (turn_tx, turn_rx) = flume::unbounded(); + let server_handle = LocalTurnServerHandle { paused: Arc::new(AtomicBool::new(true)), running: Arc::new(AtomicBool::new(true)) }; + commands.insert_resource(server_handle); + commands.insert_resource(TurnReceiver { turn_rx }); + commands.insert_resource(TurnGenerator { turn_number: 0, accumulated_time: 0.0, turn_tx, spawn_config: HashMap::new(), spawn_timeout_accumulated: None, game_started: false }); + + debug!("Turn generator initialized (paused until player spawn)"); + + // Activate spawn phase (SpawnPhasePlugin will emit initial SpawnPhaseUpdate) + commands.insert_resource(SpawnPhase { active: true }); + debug!("Spawn phase activated"); + + info!("Game resources initialized successfully - ready to start"); +} + +/// Clean up all game resources when quitting a game +pub fn cleanup_game_resources(world: &mut World) { + let _guard = tracing::trace_span!("game_cleanup").entered(); + info!("Cleaning up game resources..."); + + // Stop local turn server if running + if let Some(server_handle) = world.get_resource::() { + server_handle.stop(); + world.remove_resource::(); + } + + // Remove all game-specific resources + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + + info!("Game resources cleaned up successfully"); +} diff --git a/crates/borders-core/src/game/core/mod.rs b/crates/borders-core/src/game/core/mod.rs new file mode 100644 index 0000000..cf28411 --- /dev/null +++ b/crates/borders-core/src/game/core/mod.rs @@ -0,0 +1,22 @@ +//! Core game logic and data structures +//! +//! This module contains the fundamental game types and logic. + +pub mod action; +pub mod constants; +pub mod instance; +pub mod lifecycle; +pub mod outcome; +pub mod rng; +pub mod turn_execution; +pub mod utils; + +// Re-export commonly used types +pub use action::*; +pub use constants::*; +pub use instance::*; +pub use lifecycle::*; +pub use outcome::*; +pub use rng::*; +pub use turn_execution::*; +pub use utils::*; diff --git a/crates/borders-core/src/game/core/outcome.rs b/crates/borders-core/src/game/core/outcome.rs new file mode 100644 index 0000000..d2525a9 --- /dev/null +++ b/crates/borders-core/src/game/core/outcome.rs @@ -0,0 +1,81 @@ +use crate::game::core::constants::outcome::WIN_THRESHOLD; +use crate::game::input::context::LocalPlayerContext; +use crate::game::world::ENCODED_WATER; +use crate::networking::GameView; +use crate::ui::protocol::{BackendMessage, GameOutcome}; +use bevy_ecs::prelude::*; +use tracing::info; + +/// System that checks if the local player has won or lost +/// This is a NON-BLOCKING check - the game continues running regardless +pub fn check_local_player_outcome(mut local_context: ResMut, game_view: If>, mut backend_messages: MessageWriter) { + // Don't check if outcome already determined + if local_context.my_outcome.is_some() { + return; + } + + // Don't check outcome until player has spawned + // Skip only if player has 0 tiles AND is_alive (hasn't spawned yet) + // If player has 0 tiles AND !is_alive, that's a real defeat + let my_player_id = local_context.my_player_id; + let Some(my_player) = game_view.get_player(my_player_id) else { + return; + }; + + if my_player.tile_count == 0 && my_player.is_alive { + // Player hasn't spawned yet - skip outcome check + return; + } + + // Check defeat condition: I've been eliminated (0 tiles) + if !my_player.is_alive { + info!("Local player defeated - eliminated (0 tiles)"); + local_context.mark_defeated(); + backend_messages.write(BackendMessage::GameEnded { outcome: GameOutcome::Defeat }); + return; + } + + // Calculate total claimable tiles for victory condition checks + let total_claimable_tiles = game_view.territories.iter().filter(|&&owner| owner != ENCODED_WATER).count(); + + if total_claimable_tiles > 0 { + let my_tiles = my_player.tile_count as usize; + let my_occupation = my_tiles as f32 / total_claimable_tiles as f32; + + // Check if I've won by occupation + if my_occupation >= WIN_THRESHOLD { + info!("Local player victorious - reached {:.1}% occupation ({}/{} claimable tiles, threshold: {:.0}%)", my_occupation * 100.0, my_tiles, total_claimable_tiles, WIN_THRESHOLD * 100.0); + local_context.mark_victorious(); + backend_messages.write(BackendMessage::GameEnded { outcome: GameOutcome::Victory }); + return; + } + + // Check if any opponent has won by occupation (which means I lost) + for player in &game_view.players { + if player.id != my_player_id && player.is_alive { + let opponent_tiles = player.tile_count as usize; + let opponent_occupation = opponent_tiles as f32 / total_claimable_tiles as f32; + + if opponent_occupation >= WIN_THRESHOLD { + info!("Local player defeated - {} reached {:.1}% occupation ({}/{} claimable tiles, threshold: {:.0}%)", player.name, opponent_occupation * 100.0, opponent_tiles, total_claimable_tiles, WIN_THRESHOLD * 100.0); + local_context.mark_defeated(); + backend_messages.write(BackendMessage::GameEnded { outcome: GameOutcome::Defeat }); + return; + } + } + } + } + + // Check victory by eliminating all opponents + let all_opponents_dead = game_view + .players + .iter() + .filter(|p| p.id != my_player_id) // Exclude me + .all(|p| !p.is_alive); + + if all_opponents_dead && my_player.is_alive { + info!("Local player victorious - all opponents eliminated"); + local_context.mark_victorious(); + backend_messages.write(BackendMessage::GameEnded { outcome: GameOutcome::Victory }); + } +} diff --git a/crates/borders-core/src/game/core/rng.rs b/crates/borders-core/src/game/core/rng.rs new file mode 100644 index 0000000..3df4231 --- /dev/null +++ b/crates/borders-core/src/game/core/rng.rs @@ -0,0 +1,126 @@ +use bevy_ecs::prelude::*; +use rand::SeedableRng; +use rand::rngs::StdRng; + +/// Centralized deterministic RNG resource +/// +/// This resource provides deterministic random number generation for all game systems. +/// It is updated at the start of each turn with the current turn number, ensuring that +/// the same sequence of turns always produces the same random values. +/// +/// # Determinism Guarantees +/// +/// - Same turn number + base seed + context → same RNG state +/// - No stored RNG state in individual systems (prevents desync) +/// - All randomness flows through this single source of truth +/// +/// # Usage +/// +/// Systems should never store RNG state. Instead, request context-specific RNG: +/// +/// ```rust,ignore +/// fn my_system(rng: Res) { +/// let mut player_rng = rng.for_player(player_id); +/// let random_value = player_rng.gen_range(0..10); +/// } +/// ``` +#[derive(Resource)] +pub struct DeterministicRng { + /// Base seed for the entire game (set at game start) + base_seed: u64, + /// Current turn number (updated each turn) + turn_number: u64, +} + +impl DeterministicRng { + /// Create a new DeterministicRng with a base seed + pub fn new(base_seed: u64) -> Self { + Self { base_seed, turn_number: 0 } + } + + /// Update the turn number (should be called at start of each turn) + pub fn update_turn(&mut self, turn_number: u64) { + self.turn_number = turn_number; + } + + /// Get the current turn number + #[inline] + pub fn turn_number(&self) -> u64 { + self.turn_number + } + + /// Create an RNG for a specific context within the current turn + /// + /// The context_id allows different systems/entities to have independent + /// random sequences while maintaining determinism. + #[inline] + pub fn for_context(&self, context_id: u64) -> StdRng { + let seed = self + .turn_number + .wrapping_mul(997) // Prime multiplier for turn + .wrapping_add(self.base_seed) + .wrapping_add(context_id.wrapping_mul(1009)); // Prime multiplier for context + StdRng::seed_from_u64(seed) + } + + /// Get an RNG for a specific player's actions this turn + /// + /// This is a convenience wrapper around `for_context` for player-specific randomness. + #[inline] + pub fn for_player(&self, player_id: u16) -> StdRng { + self.for_context(player_id as u64) + } + + /// Get an RNG for a specific tile's calculations this turn + /// + /// Useful for tile-based randomness that should be consistent within a turn. + pub fn for_tile(&self, tile: glam::U16Vec2) -> StdRng { + // Use large offset to avoid collision with player IDs + // Convert tile position to unique ID + let tile_id = (tile.y as u64) * u16::MAX as u64 + (tile.x as u64); + self.for_context(1_000_000 + tile_id) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::Rng; + + #[test] + fn test_deterministic_same_turn_same_seed() { + let rng1 = DeterministicRng::new(12345); + let rng2 = DeterministicRng::new(12345); + + let mut player_rng1 = rng1.for_player(0); + let mut player_rng2 = rng2.for_player(0); + + assert_eq!(player_rng1.random::(), player_rng2.random::()); + } + + #[test] + fn test_deterministic_different_context() { + let rng = DeterministicRng::new(12345); + + let mut player0_rng = rng.for_player(0); + let mut player1_rng = rng.for_player(1); + + // Different contexts should produce different values + assert_ne!(player0_rng.random::(), player1_rng.random::()); + } + + #[test] + fn test_turn_update() { + let mut rng = DeterministicRng::new(12345); + + let mut turn0_rng = rng.for_player(0); + let value_turn0 = turn0_rng.random::(); + + rng.update_turn(1); + let mut turn1_rng = rng.for_player(0); + let value_turn1 = turn1_rng.random::(); + + // Same player, different turns should produce different values + assert_ne!(value_turn0, value_turn1); + } +} diff --git a/crates/borders-core/src/game/core/turn_execution.rs b/crates/borders-core/src/game/core/turn_execution.rs new file mode 100644 index 0000000..38fb73e --- /dev/null +++ b/crates/borders-core/src/game/core/turn_execution.rs @@ -0,0 +1,180 @@ +use std::collections::{HashMap, HashSet}; + +use bevy_ecs::prelude::*; +use glam::U16Vec2; + +use crate::game::TileOwnership; +use crate::game::ai::bot::BotManager; +use crate::game::combat::ActiveAttacks; +use crate::game::core::action::GameAction; +use crate::game::core::instance::TroopCount; +use crate::game::core::rng::DeterministicRng; +use crate::game::entities::{HumanPlayerCount, PlayerEntityMap, TerritorySize, Troops, remove_troops}; +use crate::game::world::TerritoryManager; +use crate::networking::{Intent, Turn}; + +/// Execute a full game turn +#[allow(clippy::too_many_arguments)] +pub fn execute_turn(turn: &Turn, turn_number: u64, territory_manager: &mut TerritoryManager, active_attacks: &mut ActiveAttacks, bot_manager: &mut BotManager, rng: &mut DeterministicRng, player_borders: &HashMap>, entity_map: &PlayerEntityMap, players: &mut Query<(&mut Troops, &mut TerritorySize)>, commands: &mut Commands, human_count: &HumanPlayerCount, launch_ship_writer: &mut MessageWriter) { + let _guard = tracing::trace_span!("execute_turn", turn_number, intent_count = turn.intents.len()).entered(); + + // Update RNG for this turn + rng.update_turn(turn_number); + + // PHASE 1: Process bot actions (deterministic, based on turn N-1 state) + let bot_player_ids = bot_manager.bot_player_ids().to_vec(); + { + // Count alive bots for telemetry + let alive_bot_count = bot_player_ids.iter().filter(|&&id| entity_map.0.get(&id).and_then(|&entity| players.get(entity).ok()).is_some()).count(); + + let _guard = tracing::trace_span!("bot_processing", bot_count = bot_player_ids.len(), alive_bot_count).entered(); + + for (bot_index, &player_id) in bot_player_ids.iter().enumerate() { + // Check if bot is alive (entity exists and doesn't have Dead component) + let Some(&entity) = entity_map.0.get(&player_id) else { + continue; + }; + + if players.get(entity).is_err() { + continue; + } + + // Get bot's current troops and territory from ECS + if let Ok((troops, territory_size)) = players.get(entity) + && let Some(action) = bot_manager.calculate_action(bot_index, turn_number, player_id, troops.0, territory_size.0 as usize, territory_manager, player_borders, rng.turn_number()) + { + apply_action(action, turn_number, territory_manager, active_attacks, rng, player_borders, entity_map, players, commands, launch_ship_writer); + } + } + } + + // PHASE 2: Process player intents (from network) + for intent in &turn.intents { + match intent { + Intent::Action(action) => { + apply_action(action.clone(), turn_number, territory_manager, active_attacks, rng, player_borders, entity_map, players, commands, launch_ship_writer); + } + Intent::SetSpawn { .. } => {} + } + } + + // PHASE 3: Tick game systems (attacks, etc.) + active_attacks.tick(entity_map, players, commands, territory_manager, player_borders, rng, human_count); +} + +/// Apply a game action (attack or ship launch) +#[allow(clippy::too_many_arguments)] +pub fn apply_action(action: GameAction, turn_number: u64, territory_manager: &TerritoryManager, active_attacks: &mut ActiveAttacks, rng: &DeterministicRng, player_borders: &HashMap>, entity_map: &PlayerEntityMap, players: &mut Query<(&mut Troops, &mut TerritorySize)>, commands: &mut Commands, launch_ship_writer: &mut MessageWriter) { + match action { + GameAction::Attack { player_id, target_tile, troops_ratio } => { + handle_attack(player_id, target_tile, troops_ratio, turn_number, territory_manager, active_attacks, rng, player_borders, entity_map, players, commands); + } + GameAction::LaunchShip { player_id, target_tile, troops_percent } => { + launch_ship_writer.write(crate::game::ships::LaunchShipEvent { player_id, target_tile, troops_percent }); + } + } +} + +/// Handle player spawn at a given tile +#[allow(clippy::too_many_arguments)] +pub fn handle_spawn(player_id: u16, tile: U16Vec2, territory_manager: &mut TerritoryManager, active_attacks: &mut ActiveAttacks, rng: &DeterministicRng, entity_map: &PlayerEntityMap, players: &mut Query<(&mut Troops, &mut TerritorySize)>, _commands: &mut Commands) { + let size = territory_manager.size(); + + if territory_manager.has_owner(tile) || territory_manager.is_water(tile) { + tracing::debug!(player_id, ?tile, "Spawn on occupied/water tile ignored"); + return; + } + + let width = size.x as usize; + let height = size.y as usize; + let spawn_x = tile.x as usize; + let spawn_y = tile.y as usize; + + let mut changed: HashSet = HashSet::new(); + for dy in -2..=2 { + for dx in -2..=2 { + let x = ((spawn_x as i32 + dx).clamp(0, width as i32 - 1)) as usize; + let y = ((spawn_y as i32 + dy).clamp(0, height as i32 - 1)) as usize; + let idx = U16Vec2::new(x as u16, y as u16); + if !territory_manager.has_owner(idx) && !territory_manager.is_water(idx) { + territory_manager.conquer(idx, player_id); + changed.insert(idx); + } + } + } + + if !changed.is_empty() { + // Border updates now handled by update_player_borders_system (batched at end of turn) + + // Update player stats + if let Some(&entity) = entity_map.0.get(&player_id) + && let Ok((_, mut territory_size)) = players.get_mut(entity) + { + territory_size.0 += changed.len() as u32; + } + // Notify active attacks that territory changed + for &t in &changed { + active_attacks.handle_territory_add(t, player_id, territory_manager, rng); + } + } +} + +/// Handle an attack action +#[allow(clippy::too_many_arguments)] +pub fn handle_attack(player_id: u16, target_tile: U16Vec2, troops_ratio: f32, turn_number: u64, territory_manager: &TerritoryManager, active_attacks: &mut ActiveAttacks, rng: &DeterministicRng, player_borders: &HashMap>, entity_map: &PlayerEntityMap, players: &mut Query<(&mut Troops, &mut TerritorySize)>, commands: &mut Commands) { + handle_attack_internal(player_id, target_tile, TroopCount::Ratio(troops_ratio), true, None, turn_number, territory_manager, active_attacks, rng, player_borders, entity_map, players, commands); +} + +/// Handle attack with specific border tiles and troop allocation +#[allow(clippy::too_many_arguments)] +pub fn handle_attack_internal(player_id: u16, target_tile: U16Vec2, troop_count: TroopCount, deduct_from_player: bool, border_tiles: Option<&HashSet>, turn_number: u64, territory_manager: &TerritoryManager, active_attacks: &mut ActiveAttacks, rng: &DeterministicRng, player_borders: &HashMap>, entity_map: &PlayerEntityMap, players: &mut Query<(&mut Troops, &mut TerritorySize)>, _commands: &mut Commands) { + let target_owner = territory_manager.get_owner(target_tile); + + if target_owner == player_id { + tracing::debug!(player_id, ?target_tile, "Attack on own tile ignored"); + return; // Can't attack self + } + + let troops = match troop_count { + TroopCount::Ratio(ratio) => { + let Some(&entity) = entity_map.0.get(&player_id) else { + return; + }; + if let Ok((troops, _)) = players.get(entity) { + troops.0 * ratio + } else { + return; + } + } + TroopCount::Absolute(count) => count as f32, + }; + + // Deduct troops from the player's pool when creating the attack (if requested) + if deduct_from_player { + let Some(&entity) = entity_map.0.get(&player_id) else { + return; + }; + if let Ok((mut troops_comp, _)) = players.get_mut(entity) { + troops_comp.0 = remove_troops(troops_comp.0, troops); + } else { + return; + } + } + + let border_tiles_to_use = border_tiles.or_else(|| player_borders.get(&player_id).copied()); + + if TileOwnership::from_u16(target_owner).is_unclaimed() { + // Check if player entity exists + if entity_map.0.contains_key(&player_id) { + active_attacks.schedule_unclaimed(player_id, troops, target_tile, border_tiles_to_use, territory_manager, player_borders, turn_number, rng); + } + } else { + // Check if both attacker and target entities exist + let attacker_exists = entity_map.0.contains_key(&player_id); + let target_exists = entity_map.0.contains_key(&target_owner); + + if attacker_exists && target_exists { + active_attacks.schedule_attack(player_id, target_owner, target_tile, troops, border_tiles_to_use, territory_manager, player_borders, turn_number, rng); + } + } +} diff --git a/crates/borders-core/src/game/core/utils.rs b/crates/borders-core/src/game/core/utils.rs new file mode 100644 index 0000000..af87827 --- /dev/null +++ b/crates/borders-core/src/game/core/utils.rs @@ -0,0 +1,82 @@ +use glam::U16Vec2; + +/// Serde helper for U16Vec2 serialization +pub mod u16vec2_serde { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(vec: &glam::U16Vec2, serializer: S) -> Result + where + S: Serializer, + { + (vec.x, vec.y).serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let (x, y) = <(u16, u16)>::deserialize(deserializer)?; + Ok(glam::U16Vec2::new(x, y)) + } +} + +/// Returns an iterator over all valid cardinal neighbors of a tile position. +/// +/// Yields positions for left, right, up, and down neighbors that are within bounds. +/// Handles boundary checks for the 4-connected grid. +/// +/// # Examples +/// ``` +/// use glam::U16Vec2; +/// use borders_core::game::utils::neighbors; +/// +/// let size = U16Vec2::new(10, 10); +/// let tile = U16Vec2::new(5, 5); +/// let neighbor_count = neighbors(tile, size).count(); +/// assert_eq!(neighbor_count, 4); +/// ``` +pub fn neighbors(tile: U16Vec2, size: U16Vec2) -> impl Iterator { + const CARDINAL_DIRECTIONS: [(i32, i32); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)]; + + let tile_i32 = (tile.x as i32, tile.y as i32); + let width = size.x as i32; + let height = size.y as i32; + + CARDINAL_DIRECTIONS.into_iter().filter_map(move |(dx, dy)| { + let nx = tile_i32.0 + dx; + let ny = tile_i32.1 + dy; + if nx >= 0 && ny >= 0 && nx < width && ny < height { Some(U16Vec2::new(nx as u16, ny as u16)) } else { None } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_corner_tile_neighbors() { + let neighbor_vec: Vec<_> = neighbors(U16Vec2::new(0, 0), U16Vec2::new(10, 10)).collect(); + assert_eq!(neighbor_vec.len(), 2); + assert!(neighbor_vec.contains(&U16Vec2::new(1, 0))); + assert!(neighbor_vec.contains(&U16Vec2::new(0, 1))); + } + + #[test] + fn test_edge_tile_neighbors() { + let neighbor_vec: Vec<_> = neighbors(U16Vec2::new(5, 0), U16Vec2::new(10, 10)).collect(); + assert_eq!(neighbor_vec.len(), 3); + assert!(neighbor_vec.contains(&U16Vec2::new(4, 0))); + assert!(neighbor_vec.contains(&U16Vec2::new(6, 0))); + assert!(neighbor_vec.contains(&U16Vec2::new(5, 1))); + } + + #[test] + fn test_center_tile_neighbors() { + let neighbor_vec: Vec<_> = neighbors(U16Vec2::new(5, 5), U16Vec2::new(10, 10)).collect(); + assert_eq!(neighbor_vec.len(), 4); + assert!(neighbor_vec.contains(&U16Vec2::new(4, 5))); + assert!(neighbor_vec.contains(&U16Vec2::new(6, 5))); + assert!(neighbor_vec.contains(&U16Vec2::new(5, 4))); + assert!(neighbor_vec.contains(&U16Vec2::new(5, 6))); + } +} diff --git a/crates/borders-core/src/game/entities/components.rs b/crates/borders-core/src/game/entities/components.rs new file mode 100644 index 0000000..42e4bde --- /dev/null +++ b/crates/borders-core/src/game/entities/components.rs @@ -0,0 +1,128 @@ +use bevy_ecs::prelude::*; +use std::collections::HashSet; +use std::ops::{Deref, DerefMut}; + +use crate::game::core::constants::player::*; + +/// Marker component to identify bot players +/// Human players are identified by the ABSENCE of this component +/// Use Option<&BotPlayer> in queries to handle both types +#[derive(Component, Debug, Clone, Copy, Default)] +pub struct BotPlayer; + +/// Marker component to identify dead players +/// Alive players are identified by the ABSENCE of this component +/// Use Without in queries to filter for alive players +#[derive(Component, Debug, Clone, Copy, Default)] +pub struct Dead; + +/// Player ID component for ECS queries +#[derive(Component, Debug, Clone, Copy)] +pub struct PlayerId(pub u16); + +/// Player name component +#[derive(Component, Debug, Clone)] +pub struct PlayerName(pub String); + +/// Player color component +#[derive(Component, Debug, Clone, Copy)] +pub struct PlayerColor(pub HSLColor); + +/// Border tiles component - tiles at the edge of a player's territory +#[derive(Component, Debug, Clone, Default)] +pub struct BorderTiles(pub HashSet); + +impl Deref for BorderTiles { + type Target = HashSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for BorderTiles { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Troops component - current troop count +#[derive(Component, Debug, Clone, Copy)] +pub struct Troops(pub f32); + +/// Territory size component - number of tiles owned +#[derive(Component, Debug, Clone, Copy)] +pub struct TerritorySize(pub u32); + +/// HSL Color representation +#[derive(Debug, Clone, Copy)] +pub struct HSLColor { + pub h: f32, // Hue: 0-360 + pub s: f32, // Saturation: 0-1 + pub l: f32, // Lightness: 0-1 +} + +impl HSLColor { + pub fn new(h: f32, s: f32, l: f32) -> Self { + Self { h, s, l } + } + + pub fn to_rgba(&self) -> [f32; 4] { + let c = (1.0 - (2.0 * self.l - 1.0).abs()) * self.s; + let h_prime = self.h / 60.0; + let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs()); + + let (r1, g1, b1) = if h_prime < 1.0 { + (c, x, 0.0) + } else if h_prime < 2.0 { + (x, c, 0.0) + } else if h_prime < 3.0 { + (0.0, c, x) + } else if h_prime < 4.0 { + (0.0, x, c) + } else if h_prime < 5.0 { + (x, 0.0, c) + } else { + (c, 0.0, x) + }; + + let m = self.l - c / 2.0; + [r1 + m, g1 + m, b1 + m, 1.0] + } +} + +/// Calculate maximum troop capacity based on territory size +/// Formula: MAX_TROOPS_MULTIPLIER * (tiles^MAX_TROOPS_POWER * MAX_TROOPS_SCALE + MAX_TROOPS_BASE) +pub fn calculate_max_troops(territory_size: u32, is_bot: bool) -> f32 { + let base_max = MAX_TROOPS_MULTIPLIER * ((territory_size as f32).powf(MAX_TROOPS_POWER) * MAX_TROOPS_SCALE + MAX_TROOPS_BASE); + + if is_bot { base_max * BOT_MAX_TROOPS_MULTIPLIER } else { base_max } +} + +/// Calculate income for this tick based on current troops and territory +pub fn calculate_income(troops: f32, territory_size: u32, is_bot: bool) -> f32 { + let max_troops = calculate_max_troops(territory_size, is_bot); + + // Base income calculation + let mut income = BASE_INCOME + (troops.powf(INCOME_POWER) / INCOME_DIVISOR); + + // Soft cap as approaching max troops + let ratio = 1.0 - (troops / max_troops); + income *= ratio; + + // Apply bot modifier + if is_bot { income * BOT_INCOME_MULTIPLIER } else { income } +} + +/// Add troops with max cap enforcement +/// Returns the new troop count after adding +pub fn add_troops_capped(current: f32, amount: f32, territory_size: u32, is_bot: bool) -> f32 { + let max_troops = calculate_max_troops(territory_size, is_bot); + (current + amount).min(max_troops) +} + +/// Remove troops, ensuring non-negative result +/// Returns the new troop count after removal +pub fn remove_troops(current: f32, amount: f32) -> f32 { + (current - amount).max(0.0) +} diff --git a/crates/borders-core/src/game/entities/entity_map.rs b/crates/borders-core/src/game/entities/entity_map.rs new file mode 100644 index 0000000..949d967 --- /dev/null +++ b/crates/borders-core/src/game/entities/entity_map.rs @@ -0,0 +1,23 @@ +use bevy_ecs::prelude::*; +use std::collections::HashMap; + +/// Maps player IDs to their ECS entities for O(1) lookup +/// +/// This resource enables systems to quickly find a player's entity +/// by their player_id without iterating through all entities. +#[derive(Resource, Default)] +pub struct PlayerEntityMap(pub HashMap); + +/// The player ID of the local client +/// +/// This identifies which player entity corresponds to the local human player +/// for client-specific UI and game state. +#[derive(Resource, Debug, Clone, Copy)] +pub struct ClientPlayerId(pub u16); + +/// The number of human players in the game +/// +/// Used to determine if a player is a bot (player_id >= human_count). +/// Currently always 1, but kept for future multiplayer support. +#[derive(Resource, Debug, Clone, Copy)] +pub struct HumanPlayerCount(pub u16); diff --git a/crates/borders-core/src/game/entities/mod.rs b/crates/borders-core/src/game/entities/mod.rs new file mode 100644 index 0000000..bc9b759 --- /dev/null +++ b/crates/borders-core/src/game/entities/mod.rs @@ -0,0 +1,9 @@ +//! Entity management module +//! +//! This module contains all player/entity-related types and management. + +pub mod components; +pub mod entity_map; + +pub use components::*; +pub use entity_map::*; diff --git a/crates/borders-core/src/game/input/context.rs b/crates/borders-core/src/game/input/context.rs new file mode 100644 index 0000000..c3d0cdf --- /dev/null +++ b/crates/borders-core/src/game/input/context.rs @@ -0,0 +1,67 @@ +use bevy_ecs::prelude::*; +use serde::{Deserialize, Serialize}; + +/// Represents the outcome for a specific player (local, not shared) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum PlayerOutcome { + /// Player has won the game + Victory, + /// Player has been eliminated/defeated + Defeat, +} + +/// Local player context - CLIENT-SPECIFIC state, NOT part of deterministic game state +/// +/// **Important: This is LOCAL context, not shared/deterministic state!** +/// +/// This resource contains information specific to THIS client's perspective: +/// - Which player ID this client controls +/// - Whether this player won/lost (irrelevant to other clients) +/// - Whether this client can send commands or is spectating +/// +/// This state is NOT synchronized across clients and is NOT part of +/// GameView which must be identical on all clients for determinism. +/// +/// In multiplayer: +/// - Each client has their own LocalPlayerContext with different player IDs +/// - One client may have `my_outcome = Victory` while others have `Defeat` +/// - A spectator would have `can_send_intents = false` +/// - The shared game state continues running regardless +#[derive(Resource)] +pub struct LocalPlayerContext { + /// The player ID for this client + pub my_player_id: u16, + + /// The outcome for this specific player (if determined) + /// None = still playing, Some(Victory/Defeat) = game ended for this player + pub my_outcome: Option, + + /// Whether this client can send intents (false when defeated or spectating) + pub can_send_intents: bool, +} + +impl LocalPlayerContext { + /// Create a new local player context for the given player ID + pub fn new(player_id: u16) -> Self { + Self { my_player_id: player_id, my_outcome: None, can_send_intents: true } + } + + /// Mark the local player as defeated + pub fn mark_defeated(&mut self) { + self.my_outcome = Some(PlayerOutcome::Defeat); + self.can_send_intents = false; + } + + /// Mark the local player as victorious + pub fn mark_victorious(&mut self) { + self.my_outcome = Some(PlayerOutcome::Victory); + // Player can still send intents after victory (to continue playing if desired) + // Or set to false if you want to prevent further actions + } + + /// Check if the local player is still actively playing + #[inline] + pub fn is_playing(&self) -> bool { + self.my_outcome.is_none() && self.can_send_intents + } +} diff --git a/crates/borders-core/src/game/input/handlers.rs b/crates/borders-core/src/game/input/handlers.rs new file mode 100644 index 0000000..490e00d --- /dev/null +++ b/crates/borders-core/src/game/input/handlers.rs @@ -0,0 +1,259 @@ +//! Platform-agnostic input handling systems +//! +//! These systems use InputState instead of Bevy's input queries, +//! making them work across both WASM and desktop platforms with +//! Pixi.js rendering. + +use std::sync::{Arc, Mutex}; + +use bevy_ecs::prelude::*; +use tracing::{debug, info, trace}; + +use crate::game::core::constants::input::*; +use crate::game::{GameAction, LocalPlayerContext, SpawnManager, TileOwnership}; +use crate::networking::{GameView, Intent, IntentEvent}; +use crate::ui::input::{InputState, KeyCode, MouseButton}; +use crate::ui::protocol::CameraCommand; + +/// Resource tracking whether spawn phase is active +#[derive(Resource, Default)] +pub struct SpawnPhase { + pub active: bool, +} + +/// Resource for attack control settings +#[derive(Resource)] +pub struct AttackControls { + pub attack_ratio: f32, +} + +impl Default for AttackControls { + fn default() -> Self { + Self { attack_ratio: DEFAULT_ATTACK_RATIO } + } +} + +/// Handle placing the human spawn by clicking on valid land +#[allow(clippy::too_many_arguments)] +pub fn handle_spawn_click_system(input_state: NonSend>>, spawn_phase: Res, game_view: Option>, local_context: Option>, mut spawn_manager: Option>, mut spawn_timeout: Option>, mut intent_writer: MessageWriter, bot_manager: Option>, territory_manager: Option>) { + if !spawn_phase.active { + return; + } + + let Ok(input) = input_state.lock() else { + return; + }; + + if !input.mouse_just_released(MouseButton::Left) { + return; + } + + let _guard = tracing::trace_span!("spawn_click").entered(); + + // Frontend handles camera interaction filtering, but double-check here + if input.had_camera_interaction() { + trace!("Spawn click ignored - camera interaction detected"); + return; + } + + let Some(game_view) = game_view else { + debug!("Spawn click ignored - GameView not ready"); + return; + }; + + let Some(local_context) = local_context else { + debug!("Spawn click ignored - LocalPlayerContext not ready"); + return; + }; + + // Can't spawn if not allowed to send intents + if !local_context.can_send_intents { + debug!("Spawn click ignored - cannot send intents"); + return; + } + + // Get tile from InputState (set by frontend) + let Some(tile_coord) = input.cursor_tile() else { + debug!("Spawn click ignored - cursor not over valid tile"); + return; + }; + + let tile_idx = crate::ui::tile_to_index(tile_coord, game_view.width()) as u32; + + let tile_ownership = TileOwnership::from_u16(game_view.get_owner(tile_idx)); + if tile_ownership.is_owned() || tile_ownership.is_water() { + debug!("Spawn click on tile {:?} ignored - occupied or water", tile_coord); + return; + } + + // Player has chosen a spawn location - send to server + info!("Player {} setting spawn at tile {:?}", local_context.my_player_id, tile_coord); + + // Check if this is the first spawn (timer not started yet) + let is_first_spawn = if let Some(ref spawn_mgr) = spawn_manager { spawn_mgr.get_player_spawns().is_empty() } else { true }; + + // Send SetSpawn intent to server (not Action - this won't be in game history) + // Server will validate, track, and eventually send Turn(0) when timeout expires + intent_writer.write(IntentEvent(Intent::SetSpawn { player_id: local_context.my_player_id, tile_index: tile_coord })); + + // Start spawn timeout on first spawn (spawn_phase plugin will emit countdown updates) + if is_first_spawn && let Some(ref mut timeout) = spawn_timeout { + timeout.start(); + info!("Spawn timeout started ({:.1}s)", timeout.duration_secs); + } + + // Update local spawn manager for preview/bot recalculation + // Note: This only updates the spawn manager, not the game instance + // The actual game state is updated when Turn(0) is processed + if let Some(ref mut spawn_mgr) = spawn_manager + && let Some(ref bot_mgr) = bot_manager + && let Some(ref territory_mgr) = territory_manager + { + // Update spawn manager (triggers bot spawn recalculation) + spawn_mgr.update_player_spawn(local_context.my_player_id, tile_coord, bot_mgr, territory_mgr); + + info!("Spawn manager updated with player {} spawn at tile {:?}", local_context.my_player_id, tile_coord); + info!("Total spawns in manager: {}", spawn_mgr.get_all_spawns().len()); + } +} + +/// Center the camera on the client's spawn (hotkey C) +pub fn handle_center_camera_system(input_state: NonSend>>, game_view: Option>, local_context: Option>, mut camera_commands: MessageWriter) { + let Ok(input) = input_state.lock() else { + return; + }; + + if !input.key_just_pressed(KeyCode::KeyC) { + return; + } + + let Some(game_view) = game_view else { + return; // GameView not ready yet + }; + + let Some(local_context) = local_context else { + return; // LocalPlayerContext not ready yet + }; + + // Find any owned tile to center on + if let Some(tile) = game_view.find_tile_owned_by(local_context.my_player_id) { + camera_commands.write(CameraCommand::CenterOnTile { tile_index: tile, animate: true }); + } +} + +/// After spawn, clicking tiles triggers expansion/attack based on ownership +/// Automatically detects if a ship is needed for water attacks +#[allow(clippy::too_many_arguments)] +pub fn handle_attack_click_system(input_state: NonSend>>, spawn_phase: Res, game_view: If>, terrain: If>, coastal_tiles: If>, local_context: If>, attack_controls: Res, mut intent_writer: MessageWriter, entity_map: If>, border_query: Query<&crate::game::BorderTiles>) { + if spawn_phase.active { + return; + } + + let Ok(input) = input_state.lock() else { + return; + }; + + if !input.mouse_just_released(MouseButton::Left) { + return; + } + + // Frontend handles camera interaction filtering + if input.had_camera_interaction() { + return; + } + + let _guard = tracing::trace_span!("attack_click").entered(); + + // Can't attack if not allowed to send intents (defeated/spectating) + if !local_context.can_send_intents { + return; + } + + // Get tile from InputState (set by frontend) + let Some(tile_coord) = input.cursor_tile() else { + return; + }; + + let tile_idx = crate::ui::tile_to_index(tile_coord, game_view.width()) as u32; + let owner = game_view.get_owner(tile_idx); + let player_id = local_context.my_player_id; + + // Can't attack own tiles + if owner == player_id { + return; + } + + // Check if target is water - ignore water clicks + let size = game_view.size(); + if terrain.is_navigable(tile_coord) { + return; + } + + // Check if target is connected to player's territory + + let is_connected = crate::game::connectivity::is_connected_to_player(game_view.territories.as_ref(), tile_coord, player_id, size); + + if is_connected { + // Target is connected to player's territory - use normal attack + intent_writer.write(IntentEvent(Intent::Action(GameAction::Attack { player_id, target_tile: tile_coord, troops_ratio: attack_controls.attack_ratio }))); + return; + } + + // Target is NOT connected - need to use ship + debug!("Target {:?} not connected to player territory, attempting ship launch", tile_coord); + + // Find target's nearest coastal tile + let target_coastal_tile = crate::game::connectivity::find_coastal_tile_in_region(game_view.territories.as_ref(), &terrain, tile_coord, size); + + let Some(target_coastal_tile) = target_coastal_tile else { + debug!("No coastal tile found in target's region for tile {:?}", tile_coord); + return; + }; + + // Find player's nearest coastal tile using O(1) entity lookup + let player_border_tiles = (*entity_map).0.get(&player_id).and_then(|&entity| border_query.get(entity).ok()); + + let launch_tile = player_border_tiles.and_then(|tiles| crate::game::ships::ShipManager::find_nearest_player_coastal_tile(coastal_tiles.tiles(), tiles, target_coastal_tile, size)); + + let Some(launch_tile) = launch_tile else { + debug!("Player has no coastal tiles to launch ship from"); + return; + }; + + debug!("Found launch tile {:?} and target coastal tile {:?} for target {:?}", launch_tile, target_coastal_tile, tile_coord); + + // Try to find a water path from launch tile to target coastal tile + let path = crate::game::ships::ship_pathfinding::find_water_path(&terrain, launch_tile, target_coastal_tile, crate::game::ships::MAX_PATH_LENGTH); + + if let Some(_path) = path { + // We can reach the target by ship! + // Convert attack_ratio (0.0-1.0) to troops_percent (0-100) + let troops_percent = (attack_controls.attack_ratio * 100.0) as u32; + + debug!("Launching ship to target {:?} with {}% troops", tile_coord, troops_percent); + + intent_writer.write(IntentEvent(Intent::Action(GameAction::LaunchShip { player_id, target_tile: tile_coord, troops_percent }))); + } else { + debug!("No water path found from {:?} to {:?}", launch_tile, target_coastal_tile); + } +} + +/// Adjust attack ratio with keys 1/2 +pub fn handle_attack_ratio_keys_system(input_state: NonSend>>, mut controls: ResMut) { + let Ok(input) = input_state.lock() else { + return; + }; + + let mut changed = false; + if input.key_just_pressed(KeyCode::Digit1) { + controls.attack_ratio = (controls.attack_ratio - ATTACK_RATIO_STEP).max(ATTACK_RATIO_MIN); + changed = true; + } + if input.key_just_pressed(KeyCode::Digit2) { + controls.attack_ratio = (controls.attack_ratio + ATTACK_RATIO_STEP).min(ATTACK_RATIO_MAX); + changed = true; + } + if changed { + debug!("Attack ratio changed to {:.1}", controls.attack_ratio); + } +} diff --git a/crates/borders-core/src/game/input/mod.rs b/crates/borders-core/src/game/input/mod.rs new file mode 100644 index 0000000..b4a815f --- /dev/null +++ b/crates/borders-core/src/game/input/mod.rs @@ -0,0 +1,9 @@ +//! Player input handling +//! +//! This module handles player input events and local player context. + +pub mod context; +pub mod handlers; + +pub use context::*; +pub use handlers::*; diff --git a/crates/borders-core/src/game/mod.rs b/crates/borders-core/src/game/mod.rs new file mode 100644 index 0000000..8e6b673 --- /dev/null +++ b/crates/borders-core/src/game/mod.rs @@ -0,0 +1,25 @@ +//! Game logic and state management +//! +//! This module contains all game-related functionality organized by domain. + +// Core modules +pub mod ai; +pub mod combat; +pub mod core; +pub mod entities; +pub mod input; +pub mod ships; +pub mod systems; +pub mod terrain; +pub mod world; + +// Re-exports from submodules +pub use ai::{BotManager, BotPlayer as BotAI}; +pub use combat::*; +pub use core::*; +pub use entities::*; +pub use input::*; +pub use ships::{LaunchShipEvent, MAX_PATH_LENGTH, MAX_SHIPS_PER_PLAYER, Ship, ShipArrivalEvent, ShipCount, ShipIdCounter, ShipManager, TICKS_PER_TILE, TROOP_PERCENT, handle_ship_arrivals_system, launch_ship_system, update_ships_system}; +pub use systems::*; +pub use terrain::*; +pub use world::*; diff --git a/crates/borders-core/src/game/ships/components.rs b/crates/borders-core/src/game/ships/components.rs new file mode 100644 index 0000000..d5e9438 --- /dev/null +++ b/crates/borders-core/src/game/ships/components.rs @@ -0,0 +1,100 @@ +use bevy_ecs::prelude::*; +use glam::U16Vec2; + +/// Ship component containing all ship state +#[derive(Component, Debug, Clone)] +pub struct Ship { + pub id: u32, + pub troops: u32, + pub path: Vec, + pub current_path_index: usize, + pub ticks_per_tile: u32, + pub ticks_since_move: u32, + pub launch_tick: u64, + pub target_tile: U16Vec2, +} + +impl Ship { + /// Create a new ship + pub fn new(id: u32, troops: u32, path: Vec, ticks_per_tile: u32, launch_tick: u64) -> Self { + let target_tile = *path.last().unwrap_or(&path[0]); + + Self { id, troops, path, current_path_index: 0, ticks_per_tile, ticks_since_move: 0, launch_tick, target_tile } + } + + /// Update the ship's position based on the current tick + /// Returns true if the ship has reached its destination + pub fn update(&mut self) -> bool { + if self.has_arrived() { + return true; + } + + self.ticks_since_move += 1; + + if self.ticks_since_move >= self.ticks_per_tile { + self.ticks_since_move = 0; + self.current_path_index += 1; + + if self.has_arrived() { + return true; + } + } + + false + } + + /// Get the current tile the ship is on + #[inline] + pub fn get_current_tile(&self) -> U16Vec2 { + if self.current_path_index < self.path.len() { self.path[self.current_path_index] } else { self.target_tile } + } + + /// Check if the ship has reached its destination + #[inline] + pub fn has_arrived(&self) -> bool { + self.current_path_index >= self.path.len() - 1 + } + + /// Get interpolation factor for smooth rendering (0.0 to 1.0) + #[inline] + pub fn get_visual_interpolation(&self) -> f32 { + if self.ticks_per_tile == 0 { + return 1.0; + } + self.ticks_since_move as f32 / self.ticks_per_tile as f32 + } + + /// Get the next tile in the path (for interpolation) + #[inline] + pub fn get_next_tile(&self) -> Option { + if self.current_path_index + 1 < self.path.len() { Some(self.path[self.current_path_index + 1]) } else { None } + } +} + +/// Component tracking number of ships owned by a player +#[derive(Component, Debug, Clone, Copy, Default)] +pub struct ShipCount(pub usize); + +/// Resource for generating unique ship IDs +#[derive(Resource)] +pub struct ShipIdCounter { + next_id: u32, +} + +impl ShipIdCounter { + pub fn new() -> Self { + Self { next_id: 1 } + } + + pub fn generate_id(&mut self) -> u32 { + let id = self.next_id; + self.next_id += 1; + id + } +} + +impl Default for ShipIdCounter { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/borders-core/src/game/ships/mod.rs b/crates/borders-core/src/game/ships/mod.rs new file mode 100644 index 0000000..c678702 --- /dev/null +++ b/crates/borders-core/src/game/ships/mod.rs @@ -0,0 +1,14 @@ +pub mod components; +pub mod ship_manager; +pub mod ship_pathfinding; +pub mod systems; +pub mod transport_ship; + +pub use components::*; +pub use ship_manager::*; +pub use ship_pathfinding::*; +pub use systems::*; +pub use transport_ship::*; + +// Re-export ship constants from central location +pub use crate::game::core::constants::ships::*; diff --git a/crates/borders-core/src/game/ships/ship_manager.rs b/crates/borders-core/src/game/ships/ship_manager.rs new file mode 100644 index 0000000..2ddfd68 --- /dev/null +++ b/crates/borders-core/src/game/ships/ship_manager.rs @@ -0,0 +1,161 @@ +use std::collections::{HashMap, HashSet}; + +use serde::{Deserialize, Serialize}; +use tracing::debug; + +use crate::game::ships::transport_ship::TransportShip; +use crate::game::ships::{MAX_PATH_LENGTH, MAX_SHIPS_PER_PLAYER, TICKS_PER_TILE}; + +/// Manages all active ships in the game +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ShipManager { + /// All active ships, indexed by ship ID + ships: HashMap, + + /// Track number of ships per player + ships_per_player: HashMap, + + /// Next available ship ID + next_ship_id: u32, +} + +impl ShipManager { + pub fn new() -> Self { + Self { ships: HashMap::new(), ships_per_player: HashMap::new(), next_ship_id: 1 } + } + + /// Launch a new transport ship if possible + /// Returns Some(ship_id) if successful, None if launch failed + pub fn launch_ship(&mut self, owner_id: u16, troops: u32, path: Vec, launch_tick: u64) -> Option { + // Check if player has reached ship limit + let current_ships = *self.ships_per_player.get(&owner_id).unwrap_or(&0); + if current_ships >= MAX_SHIPS_PER_PLAYER { + debug!("Player {} cannot launch ship: already has {}/{} ships", owner_id, current_ships, MAX_SHIPS_PER_PLAYER); + return None; + } + + // Check path length + if path.is_empty() { + debug!("Cannot launch ship with empty path"); + return None; + } + + if path.len() > MAX_PATH_LENGTH { + debug!("Cannot launch ship: path too long ({} > {})", path.len(), MAX_PATH_LENGTH); + return None; + } + + // Create the ship + let ship_id = self.next_ship_id; + self.next_ship_id += 1; + + let ship = TransportShip::new(ship_id, owner_id, troops, path, TICKS_PER_TILE, launch_tick); + + self.ships.insert(ship_id, ship); + *self.ships_per_player.entry(owner_id).or_insert(0) += 1; + + debug!("Launched ship {} for player {} with {} troops", ship_id, owner_id, troops); + + Some(ship_id) + } + + /// Update all ships and return list of ships that arrived at destination + /// Returns: Vec<(owner_id, target_tile, troops)> + pub fn update_ships(&mut self) -> Vec<(u16, glam::U16Vec2, u32)> { + let _guard = tracing::trace_span!("update_ships", ship_count = self.ships.len()).entered(); + let mut arrivals = Vec::new(); + let mut ships_to_remove = Vec::new(); + + for (ship_id, ship) in self.ships.iter_mut() { + if ship.update() { + // Ship has arrived at destination + arrivals.push((ship.owner_id, ship.target_tile, ship.troops)); + ships_to_remove.push(*ship_id); + } + } + + // Remove arrived ships + for ship_id in ships_to_remove { + if let Some(ship) = self.ships.remove(&ship_id) { + if let Some(count) = self.ships_per_player.get_mut(&ship.owner_id) { + *count = count.saturating_sub(1); + if *count == 0 { + self.ships_per_player.remove(&ship.owner_id); + } + } + debug!("Ship {} arrived at destination with {} troops", ship_id, ship.troops); + } + } + + arrivals + } + + /// Get all active ships + pub fn get_ships(&self) -> impl Iterator { + self.ships.values() + } + + /// Get ships for a specific player + pub fn get_player_ships(&self, player_id: u16) -> impl Iterator { + self.ships.values().filter(move |ship| ship.owner_id == player_id) + } + + /// Get number of ships for a player + pub fn get_ship_count(&self, player_id: u16) -> usize { + *self.ships_per_player.get(&player_id).unwrap_or(&0) + } + + /// Remove all ships for a player (e.g., when player is eliminated) + pub fn remove_player_ships(&mut self, player_id: u16) { + let ships_to_remove: Vec = self.ships.iter().filter(|(_, ship)| ship.owner_id == player_id).map(|(id, _)| *id).collect(); + + for ship_id in ships_to_remove { + self.ships.remove(&ship_id); + } + + self.ships_per_player.remove(&player_id); + } + + /// Clear all ships + pub fn clear(&mut self) { + self.ships.clear(); + self.ships_per_player.clear(); + self.next_ship_id = 1; + } + + /// Find the nearest coastal tile owned by a player to a target tile + /// Returns None if no valid coastal tile found + pub fn find_nearest_player_coastal_tile(coastal_tiles: &HashSet, player_border_tiles: &HashSet, target_tile: glam::U16Vec2, size: glam::U16Vec2) -> Option { + debug!("Finding coastal tile: coastal_tiles.len={}, player_border_tiles.len={}, target_tile={:?}, size={:?}", coastal_tiles.len(), player_border_tiles.len(), target_tile, size); + + let mut best_tile = None; + let mut best_distance = u32::MAX; + let mut player_coastal_count = 0; + + // Filter player's border tiles for coastal ones + for &tile in player_border_tiles { + // Check if this tile is in the coastal set + if coastal_tiles.contains(&tile) { + player_coastal_count += 1; + + // Calculate Manhattan distance to target + let dist = ((tile.x as i32 - target_tile.x as i32).abs() + (tile.y as i32 - target_tile.y as i32).abs()) as u32; + + if dist < best_distance { + best_distance = dist; + best_tile = Some(tile); + } + } + } + + debug!("Found {} coastal tiles in player borders, best_tile={:?}", player_coastal_count, best_tile); + + best_tile + } +} + +impl Default for ShipManager { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/borders-core/src/game/ships/ship_pathfinding.rs b/crates/borders-core/src/game/ships/ship_pathfinding.rs new file mode 100644 index 0000000..a92de72 --- /dev/null +++ b/crates/borders-core/src/game/ships/ship_pathfinding.rs @@ -0,0 +1,245 @@ +use crate::game::terrain::data::TerrainData; +use crate::utils::neighbors; +use glam::U16Vec2; +use std::cmp::Ordering; +use std::collections::{BinaryHeap, HashMap, HashSet}; +use tracing::debug; + +/// A node in the pathfinding search (internal representation using indices for performance) +#[derive(Clone, Eq, PartialEq)] +struct PathNode { + tile_idx: usize, + g_cost: u32, // Cost from start + h_cost: u32, // Heuristic cost to goal + f_cost: u32, // Total cost (g + h) +} + +impl Ord for PathNode { + fn cmp(&self, other: &Self) -> Ordering { + // Reverse ordering for min-heap + other.f_cost.cmp(&self.f_cost) + } +} + +impl PartialOrd for PathNode { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Find a water path from start_tile to target_tile using A* algorithm +/// Returns None if no path exists +/// +/// Note: Internally uses usize indices for performance, but accepts/returns U16Vec2 positions +pub fn find_water_path(terrain: &TerrainData, start_tile: U16Vec2, target_tile: U16Vec2, max_path_length: usize) -> Option> { + let size = terrain.size(); + let width = size.x as usize; + + // Check if target is reachable (must be coastal or water) + if !is_valid_ship_destination(terrain, target_tile, size) { + debug!("Pathfinding failed: target {:?} is not a valid ship destination", target_tile); + return None; + } + + // Find actual water start position (adjacent to coast) + debug!("Pathfinding: looking for water launch tile adjacent to coastal tile {:?}", start_tile); + let water_start = find_water_launch_tile(terrain, start_tile, size)?; + debug!("Pathfinding: found water launch tile {:?}", water_start); + + // Find water tiles adjacent to target if target is land + let water_targets = if terrain.is_navigable(target_tile) { vec![target_tile] } else { find_adjacent_water_tiles(terrain, target_tile, size) }; + + if water_targets.is_empty() { + return None; + } + + // Run A* pathfinding + let mut open_set = BinaryHeap::new(); + let mut closed_set = HashSet::new(); + let mut came_from: HashMap = HashMap::new(); + let mut g_scores: HashMap = HashMap::new(); + + // Initialize with start node + let start_h = heuristic_distance(water_start, water_targets[0], size); + open_set.push(PathNode { tile_idx: (water_start.y as usize) * width + (water_start.x as usize), g_cost: 0, h_cost: start_h, f_cost: start_h }); + g_scores.insert(water_start, 0); + + while let Some(current_node) = open_set.pop() { + let current_pos = U16Vec2::new((current_node.tile_idx % width) as u16, (current_node.tile_idx / width) as u16); + + // Check if we've reached any of the target tiles + if water_targets.contains(¤t_pos) { + // Reconstruct path + let mut path = vec![current_pos]; + let mut current_tile = current_pos; + + while let Some(&parent) = came_from.get(¤t_tile) { + path.push(parent); + current_tile = parent; + + // Prevent infinite loops + if path.len() > max_path_length { + return None; + } + } + + path.reverse(); + + // If original target was land, add it to the end + if !terrain.is_navigable(target_tile) { + path.push(target_tile); + } + + return Some(path); + } + + // Skip if already processed + if closed_set.contains(¤t_pos) { + continue; + } + closed_set.insert(current_pos); + + // Check if we've exceeded max path length + if current_node.g_cost as usize > max_path_length { + continue; + } + + // Explore neighbors + for neighbor_pos in neighbors(current_pos, size) { + if closed_set.contains(&neighbor_pos) { + continue; + } + + if !terrain.is_navigable(neighbor_pos) { + continue; + } + + let tentative_g = current_node.g_cost + 1; + + if tentative_g < *g_scores.get(&neighbor_pos).unwrap_or(&u32::MAX) { + came_from.insert(neighbor_pos, current_pos); + g_scores.insert(neighbor_pos, tentative_g); + + // Find best heuristic to any target + let h_cost = water_targets.iter().map(|&t| heuristic_distance(neighbor_pos, t, size)).min().unwrap_or(0); + + let f_cost = tentative_g + h_cost; + + let neighbor_idx = (neighbor_pos.y as usize) * width + (neighbor_pos.x as usize); + open_set.push(PathNode { tile_idx: neighbor_idx, g_cost: tentative_g, h_cost, f_cost }); + } + } + } + + debug!("Pathfinding failed: no path found from {:?} to {:?}", start_tile, target_tile); + None // No path found +} + +/// Calculate heuristic distance between two tile positions (Manhattan distance) +fn heuristic_distance(from: U16Vec2, to: U16Vec2, _size: U16Vec2) -> u32 { + ((from.x as i32 - to.x as i32).abs() + (from.y as i32 - to.y as i32).abs()) as u32 +} + +/// Find a water tile adjacent to a coastal land tile for ship launch +fn find_water_launch_tile(terrain: &TerrainData, coast_tile: U16Vec2, size: U16Vec2) -> Option { + debug!("find_water_launch_tile: checking coastal tile {:?}", coast_tile); + + let water_tile = neighbors(coast_tile, size).find(|&neighbor_pos| { + let is_navigable = terrain.is_navigable(neighbor_pos); + if is_navigable { + debug!(" Checking neighbor {:?}: is_water=true", neighbor_pos); + } + is_navigable + }); + + if let Some(tile) = water_tile { + debug!(" Found water launch tile {:?}", tile); + } else { + debug!(" No water launch tile found for coastal tile {:?}", coast_tile); + } + + water_tile +} + +/// Find all water tiles adjacent to a land tile +fn find_adjacent_water_tiles(terrain: &TerrainData, tile: U16Vec2, size: U16Vec2) -> Vec { + neighbors(tile, size).filter(|&neighbor_pos| terrain.is_navigable(neighbor_pos)).collect() +} + +/// Check if a tile is a valid ship destination (water or coastal land) +fn is_valid_ship_destination(terrain: &TerrainData, tile: U16Vec2, size: U16Vec2) -> bool { + // If it's water, it's valid + if terrain.is_navigable(tile) { + return true; + } + + // If it's land, check if it's coastal + neighbors(tile, size).any(|neighbor_pos| terrain.is_navigable(neighbor_pos)) +} + +/// Simplify a path by removing unnecessary waypoints (path smoothing) +/// This maintains determinism as it's purely geometric +pub fn smooth_path(path: Vec, terrain: &TerrainData, size: U16Vec2) -> Vec { + if path.len() <= 2 { + return path; + } + + let mut smoothed = vec![path[0]]; + let mut current = 0; + + while current < path.len() - 1 { + let mut farthest = current + 1; + + // Find the farthest point we can see directly + for i in (current + 2)..path.len() { + if has_clear_water_line(terrain, path[current], path[i], size) { + farthest = i; + } else { + break; + } + } + + smoothed.push(path[farthest]); + current = farthest; + } + + smoothed +} + +/// Check if there's a clear water line between two tiles +/// Uses Bresenham-like algorithm for deterministic line checking +fn has_clear_water_line(terrain: &TerrainData, from: U16Vec2, to: U16Vec2, _size: U16Vec2) -> bool { + let x0 = from.x as i32; + let y0 = from.y as i32; + let x1 = to.x as i32; + let y1 = to.y as i32; + + let dx = (x1 - x0).abs(); + let dy = (y1 - y0).abs(); + let sx = if x0 < x1 { 1 } else { -1 }; + let sy = if y0 < y1 { 1 } else { -1 }; + let mut err = dx - dy; + + let mut x = x0; + let mut y = y0; + + loop { + if !terrain.is_navigable(U16Vec2::new(x as u16, y as u16)) { + return false; // Hit land + } + + if x == x1 && y == y1 { + return true; // Reached target + } + + let e2 = 2 * err; + if e2 > -dy { + err -= dy; + x += sx; + } + if e2 < dx { + err += dx; + y += sy; + } + } +} diff --git a/crates/borders-core/src/game/ships/systems.rs b/crates/borders-core/src/game/ships/systems.rs new file mode 100644 index 0000000..9580df0 --- /dev/null +++ b/crates/borders-core/src/game/ships/systems.rs @@ -0,0 +1,267 @@ +use std::collections::HashSet; + +use bevy_ecs::hierarchy::ChildOf; +use bevy_ecs::prelude::*; +use glam::U16Vec2; +use tracing::debug; + +use crate::game::terrain::TerrainData; +use crate::game::{ + ActiveAttacks, CoastalTiles, DeterministicRng, PlayerEntityMap, PlayerId, TerritoryManager, TerritorySize, Troops, + entities::remove_troops, + ships::{MAX_SHIPS_PER_PLAYER, Ship, ShipCount, ShipIdCounter, TICKS_PER_TILE, TROOP_PERCENT}, +}; + +/// Event for requesting a ship launch +#[derive(Debug, Clone, Message)] +pub struct LaunchShipEvent { + pub player_id: u16, + pub target_tile: U16Vec2, + pub troops_percent: u32, +} + +/// Event for ship arrivals at their destination +#[derive(Debug, Clone, Message)] +pub struct ShipArrivalEvent { + pub owner_id: u16, + pub target_tile: U16Vec2, + pub troops: u32, +} + +/// System to handle ship launch requests +/// Validates launch conditions and spawns ship entities as children of players +#[allow(clippy::too_many_arguments)] +pub fn launch_ship_system(mut launch_events: MessageReader, mut commands: Commands, mut ship_id_counter: ResMut, mut players: Query<(&PlayerId, &mut Troops, &mut ShipCount)>, current_turn: If>, terrain: If>, coastal_tiles: If>, territory_manager: If>, border_cache: If>, entity_map: If>) { + let turn_number = current_turn.turn.turn_number; + let size = territory_manager.size(); + let territory_slice: Vec = territory_manager.as_slice().iter().map(|o| o.to_u16()).collect(); + + for event in launch_events.read() { + let _guard = tracing::trace_span!( + "launch_ship", + player_id = event.player_id, + ?event.target_tile + ) + .entered(); + + // Get player entity + let Some(&player_entity) = (*entity_map).0.get(&event.player_id) else { + debug!(event.player_id, "Player not found"); + continue; + }; + + // Get player components + let Ok((_, mut troops, mut ship_count)) = players.get_mut(player_entity) else { + debug!(event.player_id, "Dead player cannot launch ships"); + continue; + }; + + // Check ship limit + if ship_count.0 >= MAX_SHIPS_PER_PLAYER { + debug!(event.player_id, "Player cannot launch ship: already has {}/{} ships", ship_count.0, MAX_SHIPS_PER_PLAYER); + continue; + } + + // Check troops + if troops.0 <= 0.0 { + debug!(event.player_id, "Player has no troops to launch ship"); + continue; + } + + // Calculate troop count: use provided percentage, or default to 20% if 0 + let troops_to_send = if event.troops_percent > 0 { + let clamped = event.troops_percent.clamp(1, 100); + let calculated = (troops.0 * (clamped as f32 / 100.0)).floor() as u32; + debug!(event.player_id, player_troops = troops.0, troops_percent = clamped, troops_to_send = calculated, "Ship launch troop calculation"); + calculated + } else { + let calculated = (troops.0 * TROOP_PERCENT).floor() as u32; + debug!(event.player_id, player_troops = troops.0, default_percent = TROOP_PERCENT, troops_to_send = calculated, "Ship launch troop calculation (default)"); + calculated + }; + + if troops_to_send == 0 { + debug!(event.player_id, "Not enough troops to launch ship"); + continue; + } + + // Find target's nearest coastal tile + let target_coastal_tile = crate::game::connectivity::find_coastal_tile_in_region(&territory_slice, &terrain, event.target_tile, size); + + let target_coastal_tile = match target_coastal_tile { + Some(tile) => tile, + None => { + debug!( + event.player_id, + ?event.target_tile, + "No coastal tile found in target region" + ); + continue; + } + }; + + // Find player's nearest coastal tile + let player_border_tiles = border_cache.get(event.player_id); + let launch_tile = player_border_tiles.and_then(|tiles| crate::game::ships::ShipManager::find_nearest_player_coastal_tile(coastal_tiles.tiles(), tiles, target_coastal_tile, size)); + + let launch_tile = match launch_tile { + Some(tile) => tile, + None => { + debug!( + event.player_id, + ?event.target_tile, + "Player has no coastal tiles to launch from" + ); + continue; + } + }; + + // Calculate water path from launch tile to target coastal tile + let path = { + let _guard = tracing::trace_span!("ship_pathfinding", ?launch_tile, ?target_coastal_tile).entered(); + + crate::game::ships::ship_pathfinding::find_water_path(&terrain, launch_tile, target_coastal_tile, crate::game::ships::MAX_PATH_LENGTH) + }; + + let path = match path { + Some(p) => p, + None => { + debug!( + event.player_id, + ?event.target_tile, + ?launch_tile, + "No water path found" + ); + continue; + } + }; + + // Generate ship ID + let ship_id = ship_id_counter.generate_id(); + + // Deduct troops from player + troops.0 = remove_troops(troops.0, troops_to_send as f32); + + // Create ship as child of player entity + let ship = Ship::new(ship_id, troops_to_send, path, TICKS_PER_TILE, turn_number); + + let ship_entity = commands.spawn(ship).id(); + commands.entity(player_entity).add_child(ship_entity); + + // Increment ship count + ship_count.0 += 1; + + debug!( + event.player_id, + ?event.target_tile, + troops_to_send, + ?launch_tile, + ship_id, + "Ship launched successfully" + ); + } +} + +/// System to update all ships and emit arrival events +pub fn update_ships_system(mut ships: Query<(Entity, &mut Ship, &ChildOf)>, mut arrival_events: MessageWriter, mut commands: Commands, mut players: Query<(&PlayerId, &mut ShipCount)>) { + let _guard = tracing::trace_span!("update_ships", ship_count = ships.iter().len()).entered(); + + for (ship_entity, mut ship, parent) in ships.iter_mut() { + if ship.update() { + // Ship has arrived at destination + arrival_events.write(ShipArrivalEvent { + owner_id: { + if let Ok((player_id, _)) = players.get(parent.0) { + player_id.0 + } else { + debug!(ship_id = ship.id, "Ship parent entity missing PlayerId"); + commands.entity(ship_entity).despawn(); + continue; + } + }, + target_tile: ship.target_tile, + troops: ship.troops, + }); + + if let Ok((player_id, mut ship_count)) = players.get_mut(parent.0) { + ship_count.0 = ship_count.0.saturating_sub(1); + debug!(ship_id = ship.id, player_id = player_id.0, troops = ship.troops, "Ship arrived at destination"); + } + + // Despawn ship + commands.entity(ship_entity).despawn(); + } + } +} + +/// System to handle ship arrivals and create beachheads +#[allow(clippy::too_many_arguments)] +pub fn handle_ship_arrivals_system(mut arrival_events: MessageReader, current_turn: If>, mut territory_manager: If>, mut active_attacks: ResMut, rng: If>, entity_map: If>, border_cache: If>, mut players: Query<(&mut Troops, &mut TerritorySize)>, mut commands: Commands) { + let arrivals: Vec<_> = arrival_events.read().cloned().collect(); + + if arrivals.is_empty() { + return; + } + + let _guard = tracing::trace_span!("ship_arrivals", arrival_count = arrivals.len()).entered(); + + for arrival in arrivals { + tracing::debug!( + arrival.owner_id, + ?arrival.target_tile, + arrival.troops, + "Ship arrived at destination, establishing beachhead" + ); + + // Step 1: Force-claim the landing tile as beachhead + let previous_owner = territory_manager.conquer(arrival.target_tile, arrival.owner_id); + + // Step 2: Update player stats + let previous_ownership = crate::game::TileOwnership::from_u16(previous_owner); + if let Some(nation_id) = previous_ownership.nation_id() + && let Some(&prev_entity) = (*entity_map).0.get(&nation_id) + && let Ok((mut troops, mut territory_size)) = players.get_mut(prev_entity) + { + territory_size.0 = territory_size.0.saturating_sub(1); + if territory_size.0 == 0 { + troops.0 = 0.0; + commands.entity(prev_entity).insert(crate::game::Dead); + } + } + if let Some(&entity) = (*entity_map).0.get(&arrival.owner_id) + && let Ok((_, mut territory_size)) = players.get_mut(entity) + { + territory_size.0 += 1; + } + + let turn_number = current_turn.turn.turn_number; + let size = territory_manager.size(); + let owner_id = arrival.owner_id; + let target_tile = arrival.target_tile; + let troops = arrival.troops; + + // Step 3: Notify active attacks of territory change + active_attacks.handle_territory_add(target_tile, owner_id, &territory_manager, &rng); + + // Step 4: Create attack from beachhead to expand + // Find valid attack targets (not water, not our own tiles) + let valid_targets: Vec = crate::game::utils::neighbors(target_tile, size).filter(|&neighbor| !territory_manager.is_water(neighbor) && territory_manager.get_owner(neighbor) != owner_id).collect(); + + // Pick a deterministic random target from valid targets + if !valid_targets.is_empty() { + // Deterministic random selection using turn number and beachhead position + let seed = turn_number.wrapping_mul(31).wrapping_add(target_tile.x as u64).wrapping_add(target_tile.y as u64); + + let index = (seed % valid_targets.len() as u64) as usize; + let attack_target = valid_targets[index]; + + // Build player borders map for compatibility + let player_borders = border_cache.as_map(); + let beachhead_borders = Some(&HashSet::from([target_tile])); + + crate::game::handle_attack_internal(owner_id, attack_target, crate::game::TroopCount::Absolute(troops), false, beachhead_borders, turn_number, &territory_manager, &mut active_attacks, &rng, &player_borders, &entity_map, &mut players, &mut commands); + } else { + tracing::debug!(owner_id, ?target_tile, "Ship landed but no valid attack targets found (all adjacent tiles are water or owned)"); + } + } +} diff --git a/crates/borders-core/src/game/ships/transport_ship.rs b/crates/borders-core/src/game/ships/transport_ship.rs new file mode 100644 index 0000000..bea4b66 --- /dev/null +++ b/crates/borders-core/src/game/ships/transport_ship.rs @@ -0,0 +1,92 @@ +use serde::{Deserialize, Serialize}; + +/// A transport ship carrying troops across water +/// Uses fixed-point arithmetic for deterministic calculations +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TransportShip { + /// Unique identifier for this ship + pub id: u32, + + /// Player who owns this ship + pub owner_id: u16, + + /// Number of troops being transported (stored as integer) + pub troops: u32, + + /// Pre-calculated path of tile positions + pub path: Vec, + + /// Index of the current tile in the path + pub current_path_index: usize, + + /// Movement speed in ticks per tile (e.g., 2 = move one tile every 2 ticks) + pub ticks_per_tile: u32, + + /// Ticks since last tile transition + pub ticks_since_move: u32, + + /// The tick when the ship was launched + pub launch_tick: u64, + + /// The target tile (final destination) + pub target_tile: glam::U16Vec2, +} + +impl TransportShip { + /// Create a new transport ship + pub fn new(id: u32, owner_id: u16, troops: u32, path: Vec, ticks_per_tile: u32, launch_tick: u64) -> Self { + let target_tile = *path.last().unwrap_or(&path[0]); + + Self { id, owner_id, troops, path, current_path_index: 0, ticks_per_tile, ticks_since_move: 0, launch_tick, target_tile } + } + + /// Update the ship's position based on the current tick + /// Returns true if the ship has reached its destination + pub fn update(&mut self) -> bool { + if self.has_arrived() { + return true; + } + + self.ticks_since_move += 1; + + // Check if it's time to move to the next tile + if self.ticks_since_move >= self.ticks_per_tile { + self.ticks_since_move = 0; + self.current_path_index += 1; + + if self.has_arrived() { + return true; + } + } + + false + } + + /// Get the current tile the ship is on + #[inline] + pub fn get_current_tile(&self) -> glam::U16Vec2 { + if self.current_path_index < self.path.len() { self.path[self.current_path_index] } else { self.target_tile } + } + + /// Check if the ship has reached its destination + #[inline] + pub fn has_arrived(&self) -> bool { + self.current_path_index >= self.path.len() - 1 + } + + /// Get interpolation factor for smooth rendering (0.0 to 1.0) + /// This is only for visual interpolation and doesn't affect game logic + #[inline] + pub fn get_visual_interpolation(&self) -> f32 { + if self.ticks_per_tile == 0 { + return 1.0; + } + self.ticks_since_move as f32 / self.ticks_per_tile as f32 + } + + /// Get the next tile in the path (for interpolation) + #[inline] + pub fn get_next_tile(&self) -> Option { + if self.current_path_index + 1 < self.path.len() { Some(self.path[self.current_path_index + 1]) } else { None } + } +} diff --git a/crates/borders-core/src/game/systems/borders.rs b/crates/borders-core/src/game/systems/borders.rs new file mode 100644 index 0000000..cf98ad3 --- /dev/null +++ b/crates/borders-core/src/game/systems/borders.rs @@ -0,0 +1,158 @@ +/// Border tile management +/// +/// This module manages border tiles for all players. A border tile is a tile +/// adjacent to a tile with a different owner. Borders are used for: +/// - Attack targeting (attacks expand from border tiles) +/// - UI rendering (show player borders on the map) +/// - Ship launching (find coastal borders for naval operations) +use std::collections::{HashMap, HashSet}; + +use bevy_ecs::prelude::*; +use glam::U16Vec2; + +use crate::game::{ + entities::{BorderTiles, PlayerId}, + world::TerritoryManager, +}; + +/// Cached border data for efficient non-ECS lookups +/// +/// This resource caches border tiles per player to avoid reconstructing +/// HashMaps every turn. It is updated by `update_player_borders_system` +/// only when borders actually change. +#[derive(Resource, Default)] +pub struct BorderCache { + borders: HashMap>, +} + +impl BorderCache { + /// Get border tiles for a specific player + #[inline] + pub fn get(&self, player_id: u16) -> Option<&HashSet> { + self.borders.get(&player_id) + } + + /// Update the border cache with current border data + fn update(&mut self, player_id: u16, borders: &HashSet) { + self.borders.insert(player_id, borders.clone()); + } + + /// Get all player borders as a HashMap (for compatibility) + pub fn as_map(&self) -> HashMap> { + self.borders.iter().map(|(&id, borders)| (id, borders)).collect() + } +} + +/// Result of a border transition +#[derive(Debug)] +pub struct BorderTransitionResult { + /// Tiles that became interior (not borders anymore) + pub territory: Vec, + /// Tiles that are now attacker borders + pub attacker: Vec, + /// Tiles that are now defender borders + pub defender: Vec, +} + +/// Group affected tiles by their owner for efficient per-player processing +/// +/// Instead of checking every tile for every player (O(players * tiles)), +/// we group tiles by owner once (O(tiles)) and then process each group. +fn group_tiles_by_owner(affected_tiles: &HashSet, territory: &TerritoryManager) -> HashMap> { + let _guard = tracing::trace_span!("group_tiles_by_owner", tile_count = affected_tiles.len()).entered(); + + let mut grouped: HashMap> = HashMap::new(); + + for &tile in affected_tiles { + let owner = territory.get_owner(tile); + grouped.entry(owner).or_default().insert(tile); + } + + grouped +} + +/// System to clear territory changes +pub fn clear_territory_changes_system(mut territory_manager: If>) { + if territory_manager.has_changes() { + tracing::debug!(count = territory_manager.iter_changes().count(), "Clearing territory changes"); + territory_manager.clear_changes(); + } +} + +/// Update all player borders based on territory changes (batched system) +/// +/// This system runs once per turn AFTER all territory changes (conquests, spawns, ships). +/// It drains the TerritoryManager's change buffer and updates borders for all affected players. +/// It also updates the BorderCache for efficient non-ECS lookups. +pub fn update_player_borders_system(mut players: Query<(&PlayerId, &mut BorderTiles)>, territory_manager: If>, mut border_cache: ResMut) { + if !territory_manager.has_changes() { + return; // Early exit - no work needed + } + + let _guard = tracing::trace_span!("update_player_borders").entered(); + + let (changed_tiles, raw_change_count): (HashSet, usize) = { + let _guard = tracing::trace_span!("collect_changed_tiles").entered(); + let changes_vec: Vec = territory_manager.iter_changes().collect(); + let raw_count = changes_vec.len(); + let unique_set: HashSet = changes_vec.into_iter().collect(); + (unique_set, raw_count) + }; + + if raw_change_count != changed_tiles.len() { + tracing::warn!(raw_changes = raw_change_count, unique_changes = changed_tiles.len(), duplicates = raw_change_count - changed_tiles.len(), "Duplicate tile changes detected in ChangeBuffer - this causes performance degradation"); + } + + // Build affected tiles (changed + all neighbors) + let affected_tiles = { + let _guard = tracing::trace_span!("build_affected_tiles", changed_count = changed_tiles.len()).entered(); + + let mut affected_tiles = HashSet::with_capacity(changed_tiles.len() * 5); + let size = territory_manager.size(); + for &tile in &changed_tiles { + affected_tiles.insert(tile); + affected_tiles.extend(crate::game::core::utils::neighbors(tile, size)); + } + affected_tiles + }; + + // Group tiles by owner for efficient per-player processing + let tiles_by_owner = group_tiles_by_owner(&affected_tiles, &territory_manager); + + tracing::trace!(player_count = players.iter().len(), changed_tile_count = changed_tiles.len(), affected_tile_count = affected_tiles.len(), unique_owners = tiles_by_owner.len(), "Border update statistics"); + + // Update each player's borders (pure ECS) and BorderCache + { + let _guard = tracing::trace_span!("update_all_player_borders", player_count = players.iter().len()).entered(); + + for (player_id, mut component_borders) in &mut players { + // Only process tiles owned by this player (or empty set if none) + let empty_set = HashSet::new(); + let player_tiles = tiles_by_owner.get(&player_id.0).unwrap_or(&empty_set); + + update_borders_for_player(player_id.0, &mut component_borders, player_tiles, &territory_manager); + + // Update the cache with the new border data + border_cache.update(player_id.0, &component_borders); + } + } +} + +/// Update borders for a single player based on their owned tiles +/// +/// Only processes tiles owned by this player, significantly reducing +/// redundant work when multiple players exist. +fn update_borders_for_player(player_id: u16, borders: &mut HashSet, player_tiles: &HashSet, territory: &TerritoryManager) { + let _guard = tracing::trace_span!("update_borders_for_player", player_id, player_tile_count = player_tiles.len(), current_border_count = borders.len()).entered(); + + for &tile in player_tiles { + // Check if it's a border (early exit when different owner found) + let is_border = territory.any_neighbor_has_different_owner(tile, player_id); + + if is_border { + borders.insert(tile); + } else { + borders.remove(&tile); + } + } +} diff --git a/crates/borders-core/src/game/systems/income.rs b/crates/borders-core/src/game/systems/income.rs new file mode 100644 index 0000000..dc90d4b --- /dev/null +++ b/crates/borders-core/src/game/systems/income.rs @@ -0,0 +1,31 @@ +use bevy_ecs::prelude::*; +use tracing::trace; + +use crate::game::entities::{self, BotPlayer}; +use crate::game::{CurrentTurn, Dead, TerritorySize, Troops}; + +/// Process player income at 10 TPS (once per turn) +/// Only runs when turn_is_ready() condition is true +/// +/// Uses Option<&BotPlayer> to distinguish bot vs human players: +/// - Some(&BotPlayer) = bot player (60% income, 33% max troops) +/// - None = human player (100% income, 100% max troops) +pub fn process_player_income_system(current_turn: Res, mut players: Query<(&mut Troops, &TerritorySize, Option<&BotPlayer>), Without>) { + // Skip income processing on Turn 0 - players haven't spawned yet + // Spawning happens during execute_turn_gameplay_system on Turn 0 + if current_turn.turn.turn_number == 0 { + trace!("Skipping income on Turn 0 (pre-spawn)"); + return; + } + + // Process income for all alive players (Without filter) + for (mut troops, territory_size, bot_marker) in &mut players { + let is_bot = bot_marker.is_some(); + + // Calculate and apply income + let income = entities::calculate_income(troops.0, territory_size.0, is_bot); + troops.0 = entities::add_troops_capped(troops.0, income, territory_size.0, is_bot); + } + + trace!("Income processed for turn {}", current_turn.turn.turn_number); +} diff --git a/crates/borders-core/src/game/systems/mod.rs b/crates/borders-core/src/game/systems/mod.rs new file mode 100644 index 0000000..eaa77d6 --- /dev/null +++ b/crates/borders-core/src/game/systems/mod.rs @@ -0,0 +1,16 @@ +//! Game systems that run each tick/turn +//! +//! This module contains systems that execute game logic. + +pub mod borders; +pub mod income; +pub mod spawn; +pub mod spawn_timeout; +pub mod turn; + +// Re-export system functions and types +pub use borders::*; +pub use income::*; +pub use spawn::*; +pub use spawn_timeout::*; +pub use turn::*; diff --git a/crates/borders-core/src/game/systems/spawn.rs b/crates/borders-core/src/game/systems/spawn.rs new file mode 100644 index 0000000..fa65e34 --- /dev/null +++ b/crates/borders-core/src/game/systems/spawn.rs @@ -0,0 +1,78 @@ +use bevy_ecs::prelude::*; + +/// Represents a spawn point for a player or bot +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SpawnPoint { + pub nation: u16, + pub tile: glam::U16Vec2, +} + +impl SpawnPoint { + pub fn new(nation: u16, tile: glam::U16Vec2) -> Self { + Self { nation, tile } + } +} + +/// Manages spawn positions during the pre-game spawn phase +/// +/// This resource tracks bot and player spawn positions before the game starts ticking. +/// It allows for dynamic recalculation of bot positions when players change their spawn +/// location, implementing the two-pass spawn system described in the README. +#[derive(Resource)] +pub struct SpawnManager { + /// Initial bot spawn positions from first pass + pub initial_bot_spawns: Vec, + + /// Current bot spawn positions after recalculation + /// These are updated whenever a player chooses/changes their spawn + pub current_bot_spawns: Vec, + + /// Player spawn positions + /// Tracks human player spawn selections + pub player_spawns: Vec, + + /// RNG seed for deterministic spawn calculations + pub rng_seed: u64, +} + +impl SpawnManager { + /// Create a new SpawnManager with initial bot spawns + pub fn new(initial_bot_spawns: Vec, rng_seed: u64) -> Self { + Self { current_bot_spawns: initial_bot_spawns.clone(), initial_bot_spawns, player_spawns: Vec::new(), rng_seed } + } + + /// Update a player's spawn position and recalculate bot spawns if necessary + /// + /// This triggers the second pass of the two-pass spawn system, relocating + /// any bots that are too close to the new player position. + pub fn update_player_spawn(&mut self, player_id: u16, tile_index: glam::U16Vec2, bot_manager: &crate::game::BotManager, territory_manager: &crate::game::TerritoryManager) { + let spawn_point = SpawnPoint::new(player_id, tile_index); + + // Update or add player spawn + if let Some(entry) = self.player_spawns.iter_mut().find(|spawn| spawn.nation == player_id) { + *entry = spawn_point; + } else { + self.player_spawns.push(spawn_point); + } + + // Recalculate bot spawns with updated player positions + self.current_bot_spawns = bot_manager.recalculate_spawns_with_players(self.initial_bot_spawns.clone(), &self.player_spawns, territory_manager, self.rng_seed); + } + + /// Get all current spawn positions (players + bots) + pub fn get_all_spawns(&self) -> Vec { + let mut all_spawns = self.player_spawns.clone(); + all_spawns.extend(self.current_bot_spawns.iter().copied()); + all_spawns + } + + /// Get only bot spawn positions + pub fn get_bot_spawns(&self) -> &[SpawnPoint] { + &self.current_bot_spawns + } + + /// Get only player spawn positions + pub fn get_player_spawns(&self) -> &[SpawnPoint] { + &self.player_spawns + } +} diff --git a/crates/borders-core/src/game/systems/spawn_timeout.rs b/crates/borders-core/src/game/systems/spawn_timeout.rs new file mode 100644 index 0000000..cc1baac --- /dev/null +++ b/crates/borders-core/src/game/systems/spawn_timeout.rs @@ -0,0 +1,75 @@ +use bevy_ecs::prelude::*; + +/// Tracks spawn phase timeout state on the client side +/// +/// This resource is used to: +/// - Show countdown timer in UI +/// - Know when spawn phase is active +/// - Calculate remaining time for display +#[derive(Resource)] +pub struct SpawnTimeout { + /// Whether spawn phase is currently active + pub active: bool, + + /// Accumulated time since start (seconds) + pub elapsed_secs: f32, + + /// Total timeout duration in seconds + pub duration_secs: f32, + + /// Remaining time in seconds (updated each frame) + pub remaining_secs: f32, +} + +impl Default for SpawnTimeout { + fn default() -> Self { + Self { + active: false, + elapsed_secs: 0.0, + duration_secs: 5.0, // Local mode: 5 seconds + remaining_secs: 5.0, + } + } +} + +impl SpawnTimeout { + /// Create a new spawn timeout with specified duration + pub fn new(duration_secs: f32) -> Self { + Self { active: false, elapsed_secs: 0.0, duration_secs, remaining_secs: duration_secs } + } + + /// Start the timeout countdown + pub fn start(&mut self) { + if self.elapsed_secs == 0.0 { + self.active = true; + self.elapsed_secs = 0.0; + self.remaining_secs = self.duration_secs; + } + } + + /// Update remaining time (call each frame with delta time) + pub fn update(&mut self, delta_secs: f32) { + if !self.active { + return; + } + + self.elapsed_secs += delta_secs; + self.remaining_secs = (self.duration_secs - self.elapsed_secs).max(0.0); + + if self.remaining_secs <= 0.0 { + self.active = false; + } + } + + /// Stop the timeout + pub fn stop(&mut self) { + self.active = false; + self.elapsed_secs = 0.0; + } + + /// Check if timeout has expired + #[inline] + pub fn has_expired(&self) -> bool { + !self.active && self.remaining_secs <= 0.0 + } +} diff --git a/crates/borders-core/src/game/systems/turn.rs b/crates/borders-core/src/game/systems/turn.rs new file mode 100644 index 0000000..d7f6c54 --- /dev/null +++ b/crates/borders-core/src/game/systems/turn.rs @@ -0,0 +1,36 @@ +use bevy_ecs::prelude::*; + +use crate::networking::Turn; + +/// Resource containing the current turn data +/// Updated once per turn (10 TPS), provides turn context to all gameplay systems +#[derive(Resource)] +pub struct CurrentTurn { + pub turn: Turn, + /// Flag indicating if this turn has been processed by gameplay systems + /// Set to false when turn arrives, set to true after all systems run + pub processed: bool, +} + +impl CurrentTurn { + pub fn new(turn: Turn) -> Self { + Self { turn, processed: false } + } + + /// Mark turn as processed + #[inline] + pub fn mark_processed(&mut self) { + self.processed = true; + } + + /// Check if turn is ready to process (not yet processed) + #[inline] + pub fn is_ready(&self) -> bool { + !self.processed + } +} + +/// Run condition: only run when a turn is ready to process +pub fn turn_is_ready(current_turn: Option>) -> bool { + current_turn.is_some_and(|ct| ct.is_ready()) +} diff --git a/crates/borders-core/src/game/terrain/connectivity.rs b/crates/borders-core/src/game/terrain/connectivity.rs new file mode 100644 index 0000000..19ca8ed --- /dev/null +++ b/crates/borders-core/src/game/terrain/connectivity.rs @@ -0,0 +1,120 @@ +use crate::game::TileOwnership; +use crate::game::terrain::data::TerrainData; +use crate::utils::neighbors; +use glam::U16Vec2; +use std::collections::VecDeque; + +/// Check if a target tile's region connects to any of the player's tiles +/// Uses flood-fill through tiles matching the target's ownership +/// Returns true if connected (normal attack), false if disconnected (ship needed) +pub fn is_connected_to_player(territory: &[u16], target_tile: U16Vec2, player_id: u16, size: U16Vec2) -> bool { + let width = size.x as usize; + let target_idx = (target_tile.y as usize) * width + (target_tile.x as usize); + let target_ownership = TileOwnership::from_u16(territory[target_idx]); + + // Can't connect to water + if target_ownership.is_water() { + return false; + } + + // If target is owned by player, it's already connected + if target_ownership.is_owned_by(player_id) { + return true; + } + + // Flood-fill from target through tiles with same ownership + let mut queue = VecDeque::new(); + let mut visited = vec![false; width * (size.y as usize)]; + + queue.push_back(target_tile); + visited[target_idx] = true; + + while let Some(current_pos) = queue.pop_front() { + for neighbor_pos in neighbors(current_pos, size) { + let neighbor_idx = (neighbor_pos.y as usize) * width + (neighbor_pos.x as usize); + + if visited[neighbor_idx] { + continue; + } + + let neighbor_ownership = TileOwnership::from_u16(territory[neighbor_idx]); + + // Check if we found a player tile - SUCCESS! + if neighbor_ownership.is_owned_by(player_id) { + return true; + } + + // Only continue through tiles matching target's ownership + if neighbor_ownership == target_ownership { + visited[neighbor_idx] = true; + queue.push_back(neighbor_pos); + } + } + } + + // Exhausted search without finding player tile + false +} + +/// Find the nearest coastal tile in a region by flood-filling from target +/// Only expands through tiles matching the target's ownership +/// Returns coastal tile position if found +pub fn find_coastal_tile_in_region(territory: &[u16], terrain: &TerrainData, target_tile: U16Vec2, size: U16Vec2) -> Option { + let width = size.x as usize; + let target_idx = (target_tile.y as usize) * width + (target_tile.x as usize); + let target_ownership = TileOwnership::from_u16(territory[target_idx]); + + // Can't find coastal tile in water + if target_ownership.is_water() { + return None; + } + + // Check if target itself is coastal + if is_coastal_tile(terrain, target_tile, size) { + return Some(target_tile); + } + + // BFS from target through same-ownership tiles + let mut queue = VecDeque::new(); + let mut visited = vec![false; width * (size.y as usize)]; + + queue.push_back(target_tile); + visited[target_idx] = true; + + while let Some(current_pos) = queue.pop_front() { + for neighbor_pos in neighbors(current_pos, size) { + let neighbor_idx = (neighbor_pos.y as usize) * width + (neighbor_pos.x as usize); + + if visited[neighbor_idx] { + continue; + } + + let neighbor_ownership = TileOwnership::from_u16(territory[neighbor_idx]); + + // Only expand through matching ownership + if neighbor_ownership == target_ownership { + visited[neighbor_idx] = true; + + // Check if this tile is coastal + if is_coastal_tile(terrain, neighbor_pos, size) { + return Some(neighbor_pos); + } + + queue.push_back(neighbor_pos); + } + } + } + + None +} + +/// Check if a tile is coastal (land tile adjacent to water) +pub fn is_coastal_tile(terrain: &TerrainData, tile: U16Vec2, size: U16Vec2) -> bool { + // Must be land tile + if terrain.is_navigable(tile) { + return false; + } + + // Check if any neighbor is water (4-directional) + neighbors(tile, size).any(|neighbor| terrain.is_navigable(neighbor)) +} diff --git a/crates/borders-core/src/game/terrain/data.rs b/crates/borders-core/src/game/terrain/data.rs new file mode 100644 index 0000000..7fffcff --- /dev/null +++ b/crates/borders-core/src/game/terrain/data.rs @@ -0,0 +1,250 @@ +use bevy_ecs::prelude::Resource; +use glam::U16Vec2; +use image::GenericImageView; +use serde::{Deserialize, Serialize}; +use tracing::info; + +use crate::game::world::tilemap::TileMap; + +/// Calculate terrain color using pastel theme formulas +fn calculate_theme_color(color_base: &str, color_variant: u8) -> [u8; 3] { + let i = color_variant as i32; + + match color_base { + "grass" => { + // rgb(238 - 2 * i, 238 - 2 * i, 190 - i) + [(238 - 2 * i).clamp(0, 255) as u8, (238 - 2 * i).clamp(0, 255) as u8, (190 - i).clamp(0, 255) as u8] + } + "mountain" => { + // rgb(250 - 2 * i, 250 - 2 * i, 220 - i) + [(250 - 2 * i).clamp(0, 255) as u8, (250 - 2 * i).clamp(0, 255) as u8, (220 - i).clamp(0, 255) as u8] + } + "water" => { + // rgb(172 - 2 * i, 225 - 2 * i, 249 - 3 * i) + [(172 - 2 * i).clamp(0, 255) as u8, (225 - 2 * i).clamp(0, 255) as u8, (249 - 3 * i).clamp(0, 255) as u8] + } + _ => { + // Default fallback color (gray) + [128, 128, 128] + } + } +} + +/// Helper structs for loading World.json format +#[derive(Deserialize)] +struct WorldMapJson { + tiles: Vec, +} + +#[derive(Deserialize)] +struct WorldTileDef { + color: String, + name: String, + #[serde(default, rename = "colorBase")] + color_base: Option, + #[serde(default, rename = "colorVariant")] + color_variant: Option, + conquerable: bool, + navigable: bool, + #[serde(default, rename = "expansionCost")] + expansion_cost: Option, + #[serde(default, rename = "expansionTime")] + expansion_time: Option, +} + +/// Parse hex color string (#RRGGBB) to RGB bytes +fn parse_hex_rgb(s: &str) -> Option<[u8; 3]> { + let s = s.trim_start_matches('#'); + if s.len() != 6 { + return None; + } + let r = u8::from_str_radix(&s[0..2], 16).ok()?; + let g = u8::from_str_radix(&s[2..4], 16).ok()?; + let b = u8::from_str_radix(&s[4..6], 16).ok()?; + Some([r, g, b]) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TileType { + pub name: String, + pub color_base: String, + pub color_variant: u8, + pub conquerable: bool, + pub navigable: bool, + pub expansion_time: u8, + pub expansion_cost: u8, +} + +/// Map manifest structure +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct MapManifest { + pub map: MapMetadata, + pub name: String, + pub nations: Vec, +} + +/// Map size metadata +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct MapMetadata { + pub size: glam::U16Vec2, + pub num_land_tiles: usize, +} + +impl MapMetadata { + /// Get the width of the map + #[inline] + pub fn width(&self) -> u16 { + self.size.x + } + + /// Get the height of the map + #[inline] + pub fn height(&self) -> u16 { + self.size.y + } +} + +/// Nation spawn point +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct NationSpawn { + pub coordinates: [usize; 2], + pub flag: String, + pub name: String, + pub strength: u32, +} + +/// Loaded map data +#[derive(Debug, Clone, Resource)] +pub struct TerrainData { + pub _manifest: MapManifest, + /// Legacy terrain data (for backward compatibility) + pub terrain_data: TileMap, + /// Tile type indices (new format) + pub tiles: Vec, + /// Tile type definitions + pub tile_types: Vec, +} + +impl TerrainData { + /// Load the World map from embedded assets + pub fn load_world_map() -> Result> { + let _guard = tracing::debug_span!("load_world_map").entered(); + + const MAP_JSON: &[u8] = include_bytes!("../../../assets/maps/World.json"); + const MAP_PNG: &[u8] = include_bytes!("../../../assets/maps/World.png"); + + // Parse JSON tile definitions + let map_json: WorldMapJson = { + let _guard = tracing::trace_span!("parse_json").entered(); + serde_json::from_slice(MAP_JSON)? + }; + + // Load PNG image + let (png, width, height) = { + let _guard = tracing::trace_span!("load_png").entered(); + let png = image::load_from_memory(MAP_PNG)?; + let (width, height) = png.dimensions(); + (png, width, height) + }; + + info!("Loading World map: {}x{}", width, height); + + // Build color-to-index lookup table + let color_to_index: Vec<([u8; 3], usize)> = map_json.tiles.iter().enumerate().filter_map(|(idx, t)| parse_hex_rgb(&t.color).map(|rgb| (rgb, idx))).collect(); + + let pixel_count = (width as usize) * (height as usize); + let mut tiles = vec![0u8; pixel_count]; + let mut terrain_data_raw = vec![0u8; pixel_count]; + + // Match each pixel to nearest tile type by color + { + let _guard = tracing::trace_span!("pixel_processing", pixel_count = pixel_count).entered(); + + for y in 0..height { + for x in 0..width { + let pixel = png.get_pixel(x, y).0; + let rgb = [pixel[0], pixel[1], pixel[2]]; + + // Find nearest tile by RGB distance + let (tile_idx, _) = color_to_index + .iter() + .map(|(c, idx)| { + let dr = rgb[0] as i32 - c[0] as i32; + let dg = rgb[1] as i32 - c[1] as i32; + let db = rgb[2] as i32 - c[2] as i32; + let dist = (dr * dr + dg * dg + db * db) as u32; + (idx, dist) + }) + .min_by_key(|(_, d)| *d) + .unwrap(); + + let i = (y * width + x) as usize; + tiles[i] = *tile_idx as u8; + + // Set bit 7 if conquerable (land) + if map_json.tiles[*tile_idx].conquerable { + terrain_data_raw[i] |= 0x80; + } + // Lower 5 bits for terrain magnitude (unused for World map) + } + } + } + + // Convert to TileType format + let tile_types = { + let _guard = tracing::trace_span!("tile_type_conversion").entered(); + map_json.tiles.into_iter().map(|t| TileType { name: t.name, color_base: t.color_base.unwrap_or_default(), color_variant: t.color_variant.unwrap_or(0) as u8, conquerable: t.conquerable, navigable: t.navigable, expansion_cost: t.expansion_cost.unwrap_or(50) as u8, expansion_time: t.expansion_time.unwrap_or(50) as u8 }).collect() + }; + + let num_land_tiles = terrain_data_raw.iter().filter(|&&b| b & 0x80 != 0).count(); + + info!("World map loaded: {} land tiles", num_land_tiles); + + Ok(Self { _manifest: MapManifest { name: "World".to_string(), map: MapMetadata { size: glam::U16Vec2::new(width as u16, height as u16), num_land_tiles }, nations: vec![] }, terrain_data: TileMap::from_vec(width as u16, height as u16, terrain_data_raw), tiles, tile_types }) + } + + /// Get the size of the map + #[inline] + pub fn size(&self) -> U16Vec2 { + self.terrain_data.size() + } + + #[inline] + pub fn get_value(&self, pos: U16Vec2) -> u8 { + self.terrain_data[pos] + } + + /// Check if a tile is land (bit 7 set) + #[inline] + pub fn is_land(&self, pos: U16Vec2) -> bool { + self.get_value(pos) & 0x80 != 0 + } + + /// Get tile type at position + pub fn get_tile_type(&self, pos: U16Vec2) -> &TileType { + let idx = self.terrain_data.pos_to_index(pos) as usize; + &self.tile_types[self.tiles[idx] as usize] + } + + /// Check if a tile is conquerable + pub fn is_conquerable(&self, pos: U16Vec2) -> bool { + self.get_tile_type(pos).conquerable + } + + /// Check if a tile is navigable (water) + pub fn is_navigable(&self, pos: U16Vec2) -> bool { + self.get_tile_type(pos).navigable + } + + /// Get tile type IDs for rendering (each position maps to a tile type) + pub fn get_tile_ids(&self) -> &[u8] { + &self.tiles + } + + /// Get terrain palette colors from tile types (for rendering) + /// Returns a vec where index = tile type ID, value = RGB color + /// Colors are calculated using theme formulas based on colorBase and colorVariant + pub fn get_terrain_palette_colors(&self) -> Vec<[u8; 3]> { + self.tile_types.iter().map(|tile_type| calculate_theme_color(&tile_type.color_base, tile_type.color_variant)).collect() + } +} diff --git a/crates/borders-core/src/game/terrain/mod.rs b/crates/borders-core/src/game/terrain/mod.rs new file mode 100644 index 0000000..1301f36 --- /dev/null +++ b/crates/borders-core/src/game/terrain/mod.rs @@ -0,0 +1,9 @@ +//! Terrain and map connectivity +//! +//! This module handles terrain data and pathfinding/connectivity analysis. + +pub mod connectivity; +pub mod data; + +pub use connectivity::*; +pub use data::*; diff --git a/crates/borders-core/src/game/world/changes.rs b/crates/borders-core/src/game/world/changes.rs new file mode 100644 index 0000000..7b44f64 --- /dev/null +++ b/crates/borders-core/src/game/world/changes.rs @@ -0,0 +1,184 @@ +use std::collections::HashSet; + +use glam::U16Vec2; + +/// Lightweight change tracking buffer for tile mutations. +/// +/// Stores only the indices of changed tiles, using a HashSet to automatically +/// deduplicate when the same tile changes multiple times per turn. This enables +/// efficient delta updates for GPU rendering and network synchronization. +/// +/// # Design +/// - Records tile index changes as they occur +/// - Automatically deduplicates tile indices +/// - O(1) average insert, O(changes) iteration +/// - Optional: can be cleared/ignored when tracking not needed +/// +/// # Example +/// ``` +/// use borders_core::game::ChangeBuffer; +/// +/// let mut changes = ChangeBuffer::new(); +/// changes.push(10); +/// changes.push(25); +/// assert_eq!(changes.len(), 2); +/// +/// let indices: Vec<_> = changes.drain().collect(); +/// assert_eq!(indices.len(), 2); +/// assert_eq!(changes.len(), 0); +/// ``` +#[derive(Debug, Clone)] +pub struct ChangeBuffer { + changed_indices: HashSet, +} + +impl ChangeBuffer { + /// Creates a new empty ChangeBuffer. + pub fn new() -> Self { + Self { changed_indices: HashSet::new() } + } + + /// Creates a new ChangeBuffer with pre-allocated capacity. + /// + /// Use this when you know the approximate number of changes to avoid reallocations. + pub fn with_capacity(capacity: usize) -> Self { + Self { changed_indices: HashSet::with_capacity(capacity) } + } + + /// Records a tile index as changed. + /// + /// Automatically deduplicates - pushing the same index multiple times + /// only records it once. This is O(1) average case. + #[inline] + pub fn push(&mut self, position: U16Vec2) { + self.changed_indices.insert(position); + } + + /// Returns an iterator over changed indices without consuming them. + /// + /// Use this when you need to read changes without clearing the buffer. + /// The buffer will still contain all changes after iteration. + pub fn iter(&self) -> impl Iterator + '_ { + self.changed_indices.iter().copied() + } + + /// Drains all changed indices, returning an iterator and clearing the buffer. + /// + /// The buffer retains its capacity for reuse. + pub fn drain(&mut self) -> impl Iterator + '_ { + self.changed_indices.drain() + } + + /// Clears all tracked changes without returning them. + /// + /// The buffer retains its capacity for reuse. + pub fn clear(&mut self) { + self.changed_indices.clear(); + } + + /// Returns true if any changes have been recorded. + #[inline] + pub fn has_changes(&self) -> bool { + !self.changed_indices.is_empty() + } + + /// Returns the number of changes recorded. + /// + /// Note: This may include duplicate indices if the same tile was changed multiple times. + #[inline] + pub fn len(&self) -> usize { + self.changed_indices.len() + } + + /// Returns true if no changes have been recorded. + #[inline] + pub fn is_empty(&self) -> bool { + self.changed_indices.is_empty() + } + + /// Returns the current capacity of the internal buffer. + #[inline] + pub fn capacity(&self) -> usize { + self.changed_indices.capacity() + } +} + +impl Default for ChangeBuffer { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let buffer = ChangeBuffer::new(); + assert!(buffer.is_empty()); + assert_eq!(buffer.len(), 0); + } + + #[test] + fn test_with_capacity() { + let buffer = ChangeBuffer::with_capacity(100); + assert_eq!(buffer.capacity(), 100); + assert!(buffer.is_empty()); + } + + #[test] + fn test_push_and_drain() { + let mut buffer = ChangeBuffer::new(); + buffer.push(U16Vec2::new(10, 10)); + buffer.push(U16Vec2::new(25, 25)); + buffer.push(U16Vec2::new(42, 42)); + + assert_eq!(buffer.len(), 3); + assert!(buffer.has_changes()); + + let changes: Vec<_> = buffer.drain().collect(); + assert_eq!(changes, vec![U16Vec2::new(10, 10), U16Vec2::new(25, 25), U16Vec2::new(42, 42)]); + assert!(buffer.is_empty()); + } + + #[test] + fn test_clear() { + let mut buffer = ChangeBuffer::new(); + buffer.push(U16Vec2::new(1, 1)); + buffer.push(U16Vec2::new(2, 2)); + buffer.push(U16Vec2::new(3, 3)); + + assert_eq!(buffer.len(), 3); + buffer.clear(); + assert_eq!(buffer.len(), 0); + assert!(buffer.is_empty()); + } + + #[test] + fn test_duplicate_indices() { + let mut buffer = ChangeBuffer::new(); + buffer.push(U16Vec2::new(10, 10)); + buffer.push(U16Vec2::new(10, 10)); + buffer.push(U16Vec2::new(10, 10)); + + assert_eq!(buffer.len(), 1); // Automatically deduplicates + let changes: Vec<_> = buffer.drain().collect(); + assert_eq!(changes.len(), 1); // Only one unique entry + assert!(changes.contains(&U16Vec2::new(10, 10))); + } + + #[test] + fn test_capacity_retained_after_drain() { + let mut buffer = ChangeBuffer::with_capacity(100); + buffer.push(U16Vec2::new(1, 1)); + buffer.push(U16Vec2::new(2, 2)); + + let initial_capacity = buffer.capacity(); + let _: Vec<_> = buffer.drain().collect(); + + // Capacity should be retained after drain + assert!(buffer.capacity() >= initial_capacity); + assert!(buffer.is_empty()); + } +} diff --git a/crates/borders-core/src/game/world/coastal.rs b/crates/borders-core/src/game/world/coastal.rs new file mode 100644 index 0000000..49adfd1 --- /dev/null +++ b/crates/borders-core/src/game/world/coastal.rs @@ -0,0 +1,73 @@ +use std::collections::HashSet; + +use bevy_ecs::prelude::*; +use glam::U16Vec2; + +use crate::game::core::utils::neighbors; +use crate::game::terrain::TerrainData; + +/// Resource containing precomputed coastal tile positions +/// +/// A coastal tile is defined as a land tile (not water) that is adjacent +/// to at least one water tile in 4-directional connectivity. +/// +/// This is computed once during game initialization and never changes, +/// providing O(1) lookups for systems that need to check if a tile is coastal. +#[derive(Resource)] +pub struct CoastalTiles { + tiles: HashSet, +} + +impl CoastalTiles { + /// Compute all coastal tile positions from terrain data + /// + /// This scans the entire map once to find all land tiles adjacent to water. + /// The result is cached in a HashSet for fast lookups. + pub fn compute(terrain: &TerrainData, size: U16Vec2) -> Self { + let mut coastal_tiles = HashSet::new(); + let width = size.x as usize; + let height = size.y as usize; + + for y in 0..height { + for x in 0..width { + let tile_pos = U16Vec2::new(x as u16, y as u16); + + // Skip water tiles + if terrain.is_navigable(tile_pos) { + continue; + } + + // Check if any neighbor is water using the neighbors utility + if neighbors(tile_pos, size).any(|neighbor| terrain.is_navigable(neighbor)) { + coastal_tiles.insert(tile_pos); + } + } + } + + Self { tiles: coastal_tiles } + } + + /// Check if a tile is coastal + #[inline] + pub fn contains(&self, tile: U16Vec2) -> bool { + self.tiles.contains(&tile) + } + + /// Get a reference to the set of all coastal tiles + #[inline] + pub fn tiles(&self) -> &HashSet { + &self.tiles + } + + /// Get the number of coastal tiles + #[inline] + pub fn len(&self) -> usize { + self.tiles.len() + } + + /// Check if there are no coastal tiles + #[inline] + pub fn is_empty(&self) -> bool { + self.tiles.is_empty() + } +} diff --git a/crates/borders-core/src/game/world/manager.rs b/crates/borders-core/src/game/world/manager.rs new file mode 100644 index 0000000..ea85526 --- /dev/null +++ b/crates/borders-core/src/game/world/manager.rs @@ -0,0 +1,234 @@ +use bevy_ecs::prelude::*; +use glam::U16Vec2; + +use super::changes::ChangeBuffer; +use super::ownership::{ENCODED_WATER, TileOwnership}; +use super::tilemap::TileMap; + +/// Deprecated: Use TileOwnership::Water instead +/// Kept for backward compatibility during migration +#[deprecated(since = "0.1.0", note = "Use ENCODED_WATER or TileOwnership::Water instead")] +pub const OWNER_WATER: u16 = ENCODED_WATER; + +/// Manages territory ownership for all tiles +#[derive(Resource)] +pub struct TerritoryManager { + tile_owners: TileMap, + changes: ChangeBuffer, + /// Cached u16 representation for efficient serialization to frontend + u16_cache: Vec, + cache_dirty: bool, +} + +impl TerritoryManager { + /// Creates a new territory manager + pub fn new(width: u16, height: u16) -> Self { + let size = (width as usize) * (height as usize); + Self { tile_owners: TileMap::with_default(width, height, TileOwnership::Unclaimed), changes: ChangeBuffer::with_capacity(size / 100), u16_cache: vec![0; size], cache_dirty: true } + } + + /// Resets the territory manager + /// Should only be called when a new game is started + pub fn reset(&mut self, width: u16, height: u16, conquerable_tiles: &[bool]) { + self.tile_owners = TileMap::with_default(width, height, TileOwnership::Unclaimed); + self.changes.clear(); + + for (i, &conquerable) in conquerable_tiles.iter().enumerate() { + if !conquerable { + self.tile_owners[i] = TileOwnership::Water; + } + } + + let size = (width as usize) * (height as usize); + self.u16_cache.resize(size, 0); + self.cache_dirty = true; + } + + /// Checks if a tile is a border tile of the territory of its owner + /// A tile is a border tile if it is adjacent to a tile that is not owned by the same player + pub fn is_border(&self, tile: glam::U16Vec2) -> bool { + let owner = self.tile_owners[tile]; + + // Border if on map edge + if tile.x == 0 || tile.x == self.tile_owners.width() - 1 || tile.y == 0 || tile.y == self.tile_owners.height() - 1 { + return true; + } + + // Border if any neighbor has different owner + for neighbor_pos in self.tile_owners.neighbors(tile) { + if self.tile_owners[neighbor_pos] != owner { + return true; + } + } + + false + } + + /// Checks if a tile has an owner + pub fn has_owner(&self, tile: U16Vec2) -> bool { + self.tile_owners[tile].is_owned() + } + + /// Checks if a tile is owned by a specific player + pub fn is_owner(&self, tile: U16Vec2, owner: u16) -> bool { + self.tile_owners[tile].is_owned_by(owner) + } + + /// Gets the owner of a tile as u16 (for compatibility) + /// Returns the encoded u16 value (nation_id, or ENCODED_UNCLAIMED/ENCODED_WATER) + pub fn get_owner(&self, tile: U16Vec2) -> u16 { + self.tile_owners[tile].to_u16() + } + + /// Gets the ownership enum for a tile + pub fn get_ownership(&self, tile: U16Vec2) -> TileOwnership { + self.tile_owners[tile] + } + + /// Checks if a tile is water + pub fn is_water(&self, tile: U16Vec2) -> bool { + self.tile_owners[tile].is_water() + } + + /// Conquers a tile for a player + /// If the tile is already owned by a player, that player will lose the tile + /// Only records a change if the owner actually changed + pub fn conquer(&mut self, tile: U16Vec2, owner: u16) -> u16 { + let previous_owner = self.tile_owners[tile]; + let new_ownership = TileOwnership::Owned(owner); + + // Only update and track change if the owner actually changed + if previous_owner != new_ownership { + self.tile_owners[tile] = new_ownership; + self.changes.push(tile); + self.cache_dirty = true; + } + + previous_owner.to_u16() + } + + /// Clears a tile (removes ownership) + pub fn clear(&mut self, tile: U16Vec2) -> Option { + let ownership = self.tile_owners[tile]; + if ownership.is_owned() { + self.tile_owners[tile] = TileOwnership::Unclaimed; + self.changes.push(tile); + self.cache_dirty = true; + ownership.nation_id() + } else { + None + } + } + + /// Get the size of the map as U16Vec2 + #[inline] + pub fn size(&self) -> U16Vec2 { + self.tile_owners.size() + } + + /// Get width of the map + #[inline] + pub fn width(&self) -> u16 { + self.tile_owners.width() + } + + /// Get height of the map + #[inline] + pub fn height(&self) -> u16 { + self.tile_owners.height() + } + + /// Returns a reference to the underlying tile ownership data as a slice of enums + #[inline] + pub fn as_slice(&self) -> &[TileOwnership] { + self.tile_owners.as_slice() + } + + /// Returns the tile ownership data as u16 values for frontend serialization + /// This is cached and only recomputed when ownership changes + pub fn as_u16_slice(&mut self) -> &[u16] { + if self.cache_dirty { + let tile_count = self.tile_owners.len(); + let _guard = tracing::trace_span!("rebuild_u16_cache", tile_count).entered(); + + for (i, ownership) in self.tile_owners.as_slice().iter().enumerate() { + self.u16_cache[i] = ownership.to_u16(); + } + self.cache_dirty = false; + } + &self.u16_cache + } + + /// Returns the number of tiles in the map + #[inline] + pub fn len(&self) -> usize { + self.tile_owners.len() + } + + /// Returns true if the map has no tiles + #[inline] + pub fn is_empty(&self) -> bool { + self.tile_owners.len() == 0 + } + + /// Returns an iterator over changed tile positions without consuming them + /// Use this to read changes without clearing the buffer + #[inline] + pub fn iter_changes(&self) -> impl Iterator + '_ { + self.changes.iter() + } + + /// Drains all changed tile positions, returning an iterator and clearing the change buffer + #[inline] + pub fn drain_changes(&mut self) -> impl Iterator + '_ { + self.changes.drain() + } + + /// Returns true if any territory changes have been recorded since last drain + #[inline] + pub fn has_changes(&self) -> bool { + self.changes.has_changes() + } + + /// Clears all tracked changes without returning them + #[inline] + pub fn clear_changes(&mut self) { + self.changes.clear() + } + + /// Calls a closure for each neighbor using tile indices (legacy compatibility) + #[inline] + pub fn on_neighbor_indices(&self, index: u32, closure: F) + where + F: FnMut(u32), + { + self.tile_owners.on_neighbor_indices(index, closure) + } + + /// Checks if any neighbor has a different owner than the specified owner + pub fn any_neighbor_has_different_owner(&self, tile: U16Vec2, owner: u16) -> bool { + // Check all 4 neighbors inline + if tile.x > 0 && self.tile_owners[U16Vec2::new(tile.x - 1, tile.y)].to_u16() != owner { + return true; + } + if tile.x < self.tile_owners.width() - 1 && self.tile_owners[U16Vec2::new(tile.x + 1, tile.y)].to_u16() != owner { + return true; + } + if tile.y > 0 && self.tile_owners[U16Vec2::new(tile.x, tile.y - 1)].to_u16() != owner { + return true; + } + if tile.y < self.tile_owners.height() - 1 && self.tile_owners[U16Vec2::new(tile.x, tile.y + 1)].to_u16() != owner { + return true; + } + false + } + + /// Converts position to flat u32 index for JavaScript/IPC boundary + /// + /// Used when building GameView for frontend rendering. JavaScript engines + /// work more efficiently with flat typed arrays than nested structures. + #[inline] + pub fn pos_to_index>(&self, pos: P) -> u32 { + self.tile_owners.pos_to_index(pos) + } +} diff --git a/crates/borders-core/src/game/world/mod.rs b/crates/borders-core/src/game/world/mod.rs new file mode 100644 index 0000000..0a6e39e --- /dev/null +++ b/crates/borders-core/src/game/world/mod.rs @@ -0,0 +1,15 @@ +//! World and territory management module +//! +//! This module contains all spatial data structures and territory management. + +pub mod changes; +pub mod coastal; +pub mod manager; +pub mod ownership; +pub mod tilemap; + +pub use changes::*; +pub use coastal::*; +pub use manager::*; +pub use ownership::*; +pub use tilemap::*; diff --git a/crates/borders-core/src/game/world/ownership.rs b/crates/borders-core/src/game/world/ownership.rs new file mode 100644 index 0000000..5dbd3c1 --- /dev/null +++ b/crates/borders-core/src/game/world/ownership.rs @@ -0,0 +1,166 @@ +//! Tile ownership representation +//! +//! This module defines how tiles are owned and what their terrain type is. +//! It separates the concept of "who owns this tile" from "what type of terrain is this". +//! +//! ## Encoding +//! +//! For frontend serialization, TileOwnership is encoded as a u16: +//! - 0-65533: Nation IDs (supports 400+ nations) +//! - 65534: Unclaimed land +//! - 65535: Water (unconquerable terrain) + +use serde::{Deserialize, Serialize}; + +/// Encoded value for unclaimed land in u16 representation +pub const ENCODED_UNCLAIMED: u16 = 65534; + +/// Encoded value for water in u16 representation +/// Kept at 65535 for backward compatibility with OWNER_WATER +pub const ENCODED_WATER: u16 = 65535; + +/// Represents the ownership/state of a single tile +/// +/// This enum clearly separates nation ownership from terrain type, +/// allowing nation ID 0 to be valid without confusion with wilderness. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Default)] +pub enum TileOwnership { + /// Owned by a nation (0-65533, supporting 65,534 possible nations) + Owned(u16), + /// Unclaimed but conquerable land + #[default] + Unclaimed, + /// Water (unconquerable terrain) + Water, +} + +impl TileOwnership { + /// Convert to u16 for frontend serialization + #[inline] + pub fn to_u16(self) -> u16 { + match self { + TileOwnership::Owned(nation_id) => nation_id, + TileOwnership::Unclaimed => ENCODED_UNCLAIMED, + TileOwnership::Water => ENCODED_WATER, + } + } + + /// Convert from u16 (frontend serialization format) + #[inline] + pub fn from_u16(value: u16) -> Self { + match value { + ENCODED_WATER => TileOwnership::Water, + ENCODED_UNCLAIMED => TileOwnership::Unclaimed, + nation_id => TileOwnership::Owned(nation_id), + } + } + + /// Check if this tile is owned by any nation + #[inline] + pub fn is_owned(self) -> bool { + matches!(self, TileOwnership::Owned(_)) + } + + /// Check if this tile is unclaimed land + #[inline] + pub fn is_unclaimed(self) -> bool { + matches!(self, TileOwnership::Unclaimed) + } + + /// Check if this tile is water + #[inline] + pub fn is_water(self) -> bool { + matches!(self, TileOwnership::Water) + } + + /// Get the nation ID if this tile is owned, otherwise None + #[inline] + pub fn nation_id(self) -> Option { + match self { + TileOwnership::Owned(id) => Some(id), + _ => None, + } + } + + /// Check if this tile is owned by a specific nation + #[inline] + pub fn is_owned_by(self, nation_id: u16) -> bool { + matches!(self, TileOwnership::Owned(id) if id == nation_id) + } +} + +impl From for TileOwnership { + fn from(value: u16) -> Self { + Self::from_u16(value) + } +} + +impl From for u16 { + fn from(ownership: TileOwnership) -> Self { + ownership.to_u16() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encoding_nation_ids() { + for nation_id in [0, 1, 100, 1000, 65533] { + let ownership = TileOwnership::Owned(nation_id); + let encoded = ownership.to_u16(); + assert_eq!(encoded, nation_id); + assert_eq!(TileOwnership::from_u16(encoded), ownership); + } + } + + #[test] + fn test_encoding_unclaimed() { + let ownership = TileOwnership::Unclaimed; + let encoded = ownership.to_u16(); + assert_eq!(encoded, ENCODED_UNCLAIMED); + assert_eq!(TileOwnership::from_u16(encoded), ownership); + } + + #[test] + fn test_encoding_water() { + let ownership = TileOwnership::Water; + let encoded = ownership.to_u16(); + assert_eq!(encoded, ENCODED_WATER); + assert_eq!(TileOwnership::from_u16(encoded), ownership); + } + + #[test] + fn test_is_owned() { + assert!(TileOwnership::Owned(0).is_owned()); + assert!(TileOwnership::Owned(100).is_owned()); + assert!(!TileOwnership::Unclaimed.is_owned()); + assert!(!TileOwnership::Water.is_owned()); + } + + #[test] + fn test_is_owned_by() { + assert!(TileOwnership::Owned(5).is_owned_by(5)); + assert!(!TileOwnership::Owned(5).is_owned_by(6)); + assert!(!TileOwnership::Unclaimed.is_owned_by(5)); + assert!(!TileOwnership::Water.is_owned_by(5)); + } + + #[test] + fn test_nation_id() { + assert_eq!(TileOwnership::Owned(42).nation_id(), Some(42)); + assert_eq!(TileOwnership::Unclaimed.nation_id(), None); + assert_eq!(TileOwnership::Water.nation_id(), None); + } + + #[test] + fn test_nation_zero_is_valid() { + let ownership = TileOwnership::Owned(0); + assert!(ownership.is_owned()); + assert!(ownership.is_owned_by(0)); + assert_eq!(ownership.nation_id(), Some(0)); + assert_ne!(ownership, TileOwnership::Unclaimed); + assert_ne!(ownership, TileOwnership::Water); + } +} diff --git a/crates/borders-core/src/game/world/tilemap.rs b/crates/borders-core/src/game/world/tilemap.rs new file mode 100644 index 0000000..1f67378 --- /dev/null +++ b/crates/borders-core/src/game/world/tilemap.rs @@ -0,0 +1,446 @@ +use glam::{U16Vec2, UVec2}; +use std::ops::{Index, IndexMut}; + +/// A 2D grid-based map structure optimized for tile-based games. +/// +/// Provides efficient access to tiles using 2D coordinates (U16Vec2) while maintaining +/// cache-friendly contiguous memory layout. Supports generic tile types that implement Copy. +/// +/// Uses `u16` for dimensions, supporting maps up to 65,535x65,535 tiles. +/// +/// # Type Parameters +/// * `T` - The tile value type. Must implement `Copy` for efficient access. +/// +/// # Examples +/// ``` +/// use glam::U16Vec2; +/// use borders_core::game::TileMap; +/// +/// let mut map = TileMap::::new(10, 10); +/// map[U16Vec2::new(5, 5)] = 42; +/// assert_eq!(map[U16Vec2::new(5, 5)], 42); +/// ``` +#[derive(Clone, Debug)] +pub struct TileMap { + tiles: Box<[T]>, + size: U16Vec2, +} + +impl TileMap { + /// Creates a new TileMap with the specified dimensions and default value. + /// + /// # Arguments + /// * `width` - The width of the map in tiles + /// * `height` - The height of the map in tiles + /// * `default` - The default value to initialize all tiles with + pub fn with_default(width: u16, height: u16, default: T) -> Self { + let capacity = (width as usize) * (height as usize); + let tiles = vec![default; capacity].into_boxed_slice(); + Self { tiles, size: U16Vec2::new(width, height) } + } + + /// Creates a TileMap from an existing vector of tile data. + /// + /// # Arguments + /// * `width` - The width of the map in tiles + /// * `height` - The height of the map in tiles + /// * `data` - Vector containing tile data in row-major order + /// + /// # Panics + /// Panics if `data.len() != width * height` + pub fn from_vec(width: u16, height: u16, data: Vec) -> Self { + assert_eq!(data.len(), (width as usize) * (height as usize), "Data length must match width * height"); + Self { tiles: data.into_boxed_slice(), size: U16Vec2::new(width, height) } + } + + /// Converts the position to a flat array index. + /// + /// Accepts both U16Vec2 and UVec2 for backward compatibility. + /// + /// # Safety + /// Debug builds will assert that the position is in bounds. + /// Release builds skip the check for performance. + #[inline] + pub fn pos_to_index>(&self, pos: P) -> u32 { + let pos = pos.into(); + debug_assert!(pos.x < self.size.x && pos.y < self.size.y); + (pos.y as u32) * (self.size.x as u32) + (pos.x as u32) + } + + /// Converts a flat array index to a 2D position. + #[inline] + pub fn index_to_pos(&self, index: u32) -> U16Vec2 { + debug_assert!(index < self.tiles.len() as u32); + let width = self.size.x as u32; + U16Vec2::new((index % width) as u16, (index / width) as u16) + } + + /// Checks if a position is within the map bounds. + /// + /// Accepts both U16Vec2 and UVec2 for backward compatibility. + #[inline] + pub fn in_bounds>(&self, pos: P) -> bool { + let pos = pos.into(); + pos.x < self.size.x && pos.y < self.size.y + } + + /// Gets the tile value at the specified position. + /// + /// Returns `None` if the position is out of bounds. + pub fn get>(&self, pos: P) -> Option { + let pos = pos.into(); + if self.in_bounds(pos) { Some(self.tiles[self.pos_to_index(pos) as usize]) } else { None } + } + + /// Sets the tile value at the specified position. + /// + /// Returns `true` if the position was in bounds and the value was set, + /// `false` otherwise. + pub fn set>(&mut self, pos: P, tile: T) -> bool { + let pos = pos.into(); + if self.in_bounds(pos) { + let idx = self.pos_to_index(pos) as usize; + self.tiles[idx] = tile; + true + } else { + false + } + } + + /// Returns the size of the map as U16Vec2. + #[inline] + pub fn size(&self) -> U16Vec2 { + self.size + } + + /// Returns the width of the map. + #[inline] + pub fn width(&self) -> u16 { + self.size.x + } + + /// Returns the height of the map. + #[inline] + pub fn height(&self) -> u16 { + self.size.y + } + + /// Returns the total number of tiles in the map. + #[inline] + pub fn len(&self) -> usize { + self.tiles.len() + } + + /// Returns `true` if the map contains no tiles. + #[inline] + pub fn is_empty(&self) -> bool { + self.tiles.is_empty() + } + + /// Returns an iterator over all valid cardinal neighbors of a position. + /// + /// Yields positions for up, down, left, and right neighbors that are within bounds. + pub fn neighbors>(&self, pos: P) -> impl Iterator { + crate::game::utils::neighbors(pos.into(), self.size) + } + + /// Calls a closure for each neighbor using tile indices instead of positions. + /// + /// This is useful when working with systems that still use raw indices. + pub fn on_neighbor_indices(&self, index: u32, mut closure: F) + where + F: FnMut(u32), + { + let width = self.size.x as u32; + let height = self.size.y as u32; + let x = index % width; + let y = index / width; + + if x > 0 { + closure(index - 1); + } + if x < width - 1 { + closure(index + 1); + } + if y > 0 { + closure(index - width); + } + if y < height - 1 { + closure(index + width); + } + } + + /// Returns an iterator over all positions and their tile values. + pub fn iter(&self) -> impl Iterator + '_ { + (0..self.size.y).flat_map(move |y| { + (0..self.size.x).map(move |x| { + let pos = U16Vec2::new(x, y); + (pos, self[pos]) + }) + }) + } + + /// Returns an iterator over just the tile values. + pub fn iter_values(&self) -> impl Iterator + '_ { + self.tiles.iter().copied() + } + + /// Returns an iterator over all positions in the map. + pub fn positions(&self) -> impl Iterator + '_ { + (0..self.size.y).flat_map(move |y| (0..self.size.x).map(move |x| U16Vec2::new(x, y))) + } + + /// Returns an iterator over tile indices, positions, and values. + pub fn enumerate(&self) -> impl Iterator + '_ { + self.tiles.iter().enumerate().map(move |(idx, &value)| { + let pos = self.index_to_pos(idx as u32); + (idx, pos, value) + }) + } + + /// Returns a reference to the underlying tile data as a slice. + pub fn as_slice(&self) -> &[T] { + &self.tiles + } + + /// Returns a mutable reference to the underlying tile data as a slice. + pub fn as_mut_slice(&mut self) -> &mut [T] { + &mut self.tiles + } +} + +impl TileMap { + /// Creates a new TileMap with the specified dimensions, using T::default() for initialization. + pub fn new(width: u16, height: u16) -> Self { + Self::with_default(width, height, T::default()) + } +} + +impl Index for TileMap { + type Output = T; + + #[inline] + fn index(&self, pos: U16Vec2) -> &Self::Output { + &self.tiles[self.pos_to_index(pos) as usize] + } +} + +impl IndexMut for TileMap { + #[inline] + fn index_mut(&mut self, pos: U16Vec2) -> &mut Self::Output { + let idx = self.pos_to_index(pos) as usize; + &mut self.tiles[idx] + } +} + +// Backward compatibility: allow indexing with UVec2 +impl Index for TileMap { + type Output = T; + + #[inline] + fn index(&self, pos: UVec2) -> &Self::Output { + let pos16 = U16Vec2::new(pos.x as u16, pos.y as u16); + &self.tiles[self.pos_to_index(pos16) as usize] + } +} + +impl IndexMut for TileMap { + #[inline] + fn index_mut(&mut self, pos: UVec2) -> &mut Self::Output { + let pos16 = U16Vec2::new(pos.x as u16, pos.y as u16); + let idx = self.pos_to_index(pos16) as usize; + &mut self.tiles[idx] + } +} + +impl Index for TileMap { + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + &self.tiles[index] + } +} + +impl IndexMut for TileMap { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.tiles[index] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_with_default() { + let map = TileMap::::with_default(10, 10, 42); + assert_eq!(map.width(), 10); + assert_eq!(map.height(), 10); + assert_eq!(map[U16Vec2::new(0, 0)], 42); + assert_eq!(map[U16Vec2::new(9, 9)], 42); + } + + #[test] + fn test_from_vec() { + let data = vec![1u8, 2, 3, 4]; + let map = TileMap::from_vec(2, 2, data); + assert_eq!(map[U16Vec2::new(0, 0)], 1); + assert_eq!(map[U16Vec2::new(1, 0)], 2); + assert_eq!(map[U16Vec2::new(0, 1)], 3); + assert_eq!(map[U16Vec2::new(1, 1)], 4); + } + + #[test] + fn test_pos_to_index() { + let map = TileMap::::with_default(10, 10, 0); + assert_eq!(map.pos_to_index(U16Vec2::new(0, 0)), 0); + assert_eq!(map.pos_to_index(U16Vec2::new(5, 0)), 5); + assert_eq!(map.pos_to_index(U16Vec2::new(0, 1)), 10); + assert_eq!(map.pos_to_index(U16Vec2::new(3, 2)), 23); + } + + #[test] + fn test_index_to_pos() { + let map = TileMap::::with_default(10, 10, 0); + assert_eq!(map.index_to_pos(0), U16Vec2::new(0, 0)); + assert_eq!(map.index_to_pos(5), U16Vec2::new(5, 0)); + assert_eq!(map.index_to_pos(10), U16Vec2::new(0, 1)); + assert_eq!(map.index_to_pos(23), U16Vec2::new(3, 2)); + } + + #[test] + fn test_in_bounds() { + let map = TileMap::::with_default(10, 10, 0); + assert!(map.in_bounds(U16Vec2::new(0, 0))); + assert!(map.in_bounds(U16Vec2::new(9, 9))); + assert!(!map.in_bounds(U16Vec2::new(10, 0))); + assert!(!map.in_bounds(U16Vec2::new(0, 10))); + } + + #[test] + fn test_get_set() { + let mut map = TileMap::::with_default(10, 10, 0); + assert_eq!(map.get(U16Vec2::new(5, 5)), Some(0)); + + assert!(map.set(U16Vec2::new(5, 5), 42)); + assert_eq!(map.get(U16Vec2::new(5, 5)), Some(42)); + + assert!(!map.set(U16Vec2::new(10, 10), 99)); + assert_eq!(map.get(U16Vec2::new(10, 10)), None); + } + + #[test] + fn test_index_operators() { + let mut map = TileMap::::with_default(10, 10, 0); + map[U16Vec2::new(5, 5)] = 42; + assert_eq!(map[U16Vec2::new(5, 5)], 42); + } + + #[test] + fn test_index_by_usize() { + let mut map = TileMap::::with_default(10, 10, 0); + map[23] = 42; + assert_eq!(map[23], 42); + assert_eq!(map[U16Vec2::new(3, 2)], 42); + } + + #[test] + fn test_backward_compat_uvec2() { + // Test backward compatibility with UVec2 + let mut map = TileMap::::with_default(10, 10, 0); + map[UVec2::new(5, 5)] = 42; + assert_eq!(map[UVec2::new(5, 5)], 42); + assert_eq!(map.get(U16Vec2::new(5, 5)), Some(42)); + } + + #[test] + fn test_neighbors_center() { + let map = TileMap::::with_default(10, 10, 0); + let neighbors: Vec<_> = map.neighbors(U16Vec2::new(5, 5)).collect(); + assert_eq!(neighbors.len(), 4); + assert!(neighbors.contains(&U16Vec2::new(5, 6))); + assert!(neighbors.contains(&U16Vec2::new(6, 5))); + assert!(neighbors.contains(&U16Vec2::new(5, 4))); + assert!(neighbors.contains(&U16Vec2::new(4, 5))); + } + + #[test] + fn test_neighbors_corner() { + let map = TileMap::::with_default(10, 10, 0); + let neighbors: Vec<_> = map.neighbors(U16Vec2::new(0, 0)).collect(); + assert_eq!(neighbors.len(), 2); + assert!(neighbors.contains(&U16Vec2::new(1, 0))); + assert!(neighbors.contains(&U16Vec2::new(0, 1))); + } + + #[test] + fn test_neighbors_edge() { + let map = TileMap::::with_default(10, 10, 0); + let neighbors: Vec<_> = map.neighbors(U16Vec2::new(0, 5)).collect(); + assert_eq!(neighbors.len(), 3); + assert!(neighbors.contains(&U16Vec2::new(0, 6))); + assert!(neighbors.contains(&U16Vec2::new(1, 5))); + assert!(neighbors.contains(&U16Vec2::new(0, 4))); + } + + #[test] + fn test_on_neighbor_indices() { + let map = TileMap::::with_default(10, 10, 0); + let center_idx = map.pos_to_index(U16Vec2::new(5, 5)); + let mut count = 0; + map.on_neighbor_indices(center_idx, |_| count += 1); + assert_eq!(count, 4); + } + + #[test] + fn test_iter() { + let map = TileMap::::with_default(2, 2, 0); + let positions: Vec<_> = map.iter().map(|(pos, _)| pos).collect(); + assert_eq!(positions.len(), 4); + assert!(positions.contains(&U16Vec2::new(0, 0))); + assert!(positions.contains(&U16Vec2::new(1, 1))); + } + + #[test] + fn test_iter_values() { + let map = TileMap::::with_default(2, 2, 42); + let values: Vec<_> = map.iter_values().collect(); + assert_eq!(values, vec![42, 42, 42, 42]); + } + + #[test] + fn test_positions() { + let map = TileMap::::with_default(2, 2, 0); + let positions: Vec<_> = map.positions().collect(); + assert_eq!(positions.len(), 4); + assert_eq!(positions[0], U16Vec2::new(0, 0)); + assert_eq!(positions[3], U16Vec2::new(1, 1)); + } + + #[test] + fn test_enumerate() { + let mut map = TileMap::::with_default(2, 2, 0); + map[U16Vec2::new(1, 1)] = 42; + let entries: Vec<_> = map.enumerate().collect(); + assert_eq!(entries.len(), 4); + assert_eq!(entries[3], (3, U16Vec2::new(1, 1), 42)); + } + + #[test] + fn test_generic_u16() { + let mut map = TileMap::::with_default(5, 5, 0); + assert_eq!(map[UVec2::new(0, 0)], 0); + map[UVec2::new(2, 2)] = 65535; + assert_eq!(map[UVec2::new(2, 2)], 65535); + } + + #[test] + fn test_generic_f32() { + let mut map = TileMap::::with_default(5, 5, 1.5); + assert_eq!(map[UVec2::new(0, 0)], 1.5); + map[UVec2::new(2, 2)] = 2.7; + assert_eq!(map[UVec2::new(2, 2)], 2.7); + } +} diff --git a/crates/borders-core/src/lib.rs b/crates/borders-core/src/lib.rs new file mode 100644 index 0000000..cfd0d58 --- /dev/null +++ b/crates/borders-core/src/lib.rs @@ -0,0 +1,16 @@ +pub mod app; +pub mod build_info; +pub mod game; +pub mod networking; +pub mod platform; +pub mod plugin; +pub mod telemetry; +pub mod time; +#[cfg(feature = "ui")] +pub mod ui; + +pub use game::*; +pub use networking::*; +pub use plugin::*; +#[cfg(feature = "ui")] +pub use ui::{LastAttacksDigest, LastLeaderboardDigest, NationHighlightState, input}; diff --git a/crates/borders-core/src/networking/client.rs b/crates/borders-core/src/networking/client.rs new file mode 100644 index 0000000..f2cf509 --- /dev/null +++ b/crates/borders-core/src/networking/client.rs @@ -0,0 +1,116 @@ +use std::collections::HashMap; + +use crate::{ + BotManager, TerritoryManager, + game::SpawnManager, + networking::{GameView, Intent, IntentEvent, ProcessTurnEvent, SpawnConfigEvent, Turn, protocol::NetMessage}, +}; +use bevy_ecs::prelude::*; +use flume::{Receiver, Sender}; +use glam::U16Vec2; +use tracing::{debug, error, info, warn}; + +#[derive(Resource)] +pub struct ClientConnection { + pub intent_tx: Sender, + pub game_view_rx: Receiver, +} + +#[derive(Resource)] +pub struct RemoteClientConnection { + pub intent_tx: Sender, + pub net_message_rx: Receiver, + pub player_id: Option, +} + +pub fn receive_game_view_system(client: Res, mut game_view: ResMut) { + while let Ok(new_view) = client.game_view_rx.try_recv() { + *game_view = new_view; + } +} + +pub fn send_intent_system(mut intent_events: MessageReader, client: Res) { + for event in intent_events.read() { + debug!("Sending intent to local server: {:?}", event.0); + if let Err(e) = client.intent_tx.try_send(event.0.clone()) { + error!("Failed to send intent: {:?}", e); + } + } +} + +/// System for remote clients to handle NetMessage protocol +pub fn receive_net_message_system(remote_client: Res, mut process_turn_writer: MessageWriter, mut spawn_config_writer: MessageWriter) { + let mut message_count = 0; + while let Ok(message) = remote_client.net_message_rx.try_recv() { + message_count += 1; + match message { + NetMessage::ServerConfig { player_id } => { + info!("Received server config: player_id={}", player_id); + // Store player_id in the resource (would need to make it mutable) + } + NetMessage::Turn { turn, intents } => { + info!("Received turn {} with {} intents", turn, intents.len()); + // Convert to ProcessTurnEvent + let turn_event = ProcessTurnEvent(Turn { turn_number: turn, intents }); + process_turn_writer.write(turn_event); + } + NetMessage::Intent(_) => { + warn!("Received Intent message on client side"); + } + NetMessage::SpawnConfiguration { spawns } => { + info!("Received spawn configuration with {} spawns", spawns.len()); + let spawns_u16vec2: HashMap = spawns.into_iter().map(|(id, (x, y))| (id, U16Vec2::new(x, y))).collect(); + spawn_config_writer.write(SpawnConfigEvent(spawns_u16vec2)); + } + } + } + + if message_count > 0 { + let _guard = tracing::debug_span!("receive_net_messages", message_count).entered(); + } +} + +/// System for remote clients to send intents as NetMessage +pub fn send_net_intent_system(mut intent_events: MessageReader, remote_client: Res) { + let mut intent_count = 0; + for event in intent_events.read() { + intent_count += 1; + let net_message = NetMessage::Intent(event.0.clone()); + if let Err(e) = remote_client.intent_tx.try_send(net_message) { + error!("Failed to send net intent: {:?}", e); + } + } + + if intent_count > 0 { + let _guard = tracing::debug_span!("send_net_intents", intent_count).entered(); + } +} + +/// System to handle spawn configuration updates from server +/// Updates local SpawnManager with remote player spawn positions +pub fn handle_spawn_config_system(mut events: MessageReader, mut spawns: Option>, territory: Option>, bots: Option>) { + for event in events.read() { + let Some(ref mut spawns) = spawns else { + continue; + }; + + let Some(ref bots) = bots else { + continue; + }; + + let Some(ref territory) = territory else { + continue; + }; + + // Update player spawns from server + spawns.player_spawns.clear(); + for (&player_id, &tile_index) in &event.0 { + spawns.player_spawns.push(crate::game::SpawnPoint::new(player_id, tile_index)); + } + + // Recalculate bot spawns based on updated player positions + spawns.current_bot_spawns = bots.recalculate_spawns_with_players(spawns.initial_bot_spawns.clone(), &spawns.player_spawns, territory, spawns.rng_seed); + + info!("Updated spawn manager with {} player spawns from server", spawns.player_spawns.len()); + } +} diff --git a/crates/borders-core/src/networking/coordinator.rs b/crates/borders-core/src/networking/coordinator.rs new file mode 100644 index 0000000..5bf8467 --- /dev/null +++ b/crates/borders-core/src/networking/coordinator.rs @@ -0,0 +1,198 @@ +use crate::time::Time; +use crate::{BotManager, SpawnManager, TerritoryManager}; +use bevy_ecs::prelude::*; +use tracing::{debug, info, warn}; + +use crate::game::core::constants::game::TICK_INTERVAL; +use crate::game::core::constants::spawning::SPAWN_TIMEOUT_SECS; +use crate::networking::{Intent, ProcessTurnEvent, Turn}; +use flume::{Receiver, Sender}; +use std::collections::HashMap; +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; + +#[derive(Resource)] +pub struct IntentReceiver { + pub intent_rx: Receiver, +} + +#[derive(Resource)] +pub struct TurnReceiver { + pub turn_rx: Receiver, +} + +/// Local turn server control handle +#[derive(Resource, Clone)] +pub struct LocalTurnServerHandle { + pub paused: Arc, + pub running: Arc, +} + +impl LocalTurnServerHandle { + pub fn pause(&self) { + self.paused.store(true, Ordering::SeqCst); + } + + pub fn resume(&self) { + self.paused.store(false, Ordering::SeqCst); + } + + pub fn stop(&self) { + self.running.store(false, Ordering::SeqCst); + } + + pub fn is_paused(&self) -> bool { + self.paused.load(Ordering::SeqCst) + } + + pub fn is_running(&self) -> bool { + self.running.load(Ordering::SeqCst) + } +} + +/// Spawn timeout duration for local games (milliseconds) +const SPAWN_TIMEOUT_MS: u64 = (SPAWN_TIMEOUT_SECS * 1000.0) as u64; + +/// Resource to track turn generation state +#[derive(Resource)] +pub struct TurnGenerator { + pub turn_number: u64, + pub accumulated_time: f64, // milliseconds + pub turn_tx: Sender, + // Spawn phase tracking + pub spawn_config: HashMap, + pub spawn_timeout_accumulated: Option, // milliseconds since first spawn + pub game_started: bool, +} + +/// System to generate turns using Bevy's Update loop +pub fn generate_turns_system(mut generator: ResMut, server_handle: Res, intent_receiver: Res, mut spawns: Option>, time: Res

jU?lMi@tgxr7CqX_r3uw^y4tVU3Pm0sw;|1WSUO%?=bG`*Kmz6u4{#ti;T7AWIBAEh!(Y zz>O01&#X?Ds@L)Sb{CkG#Yz4$3o d@96)?#cz^xWoA}>B$xmI002ovPDHLkV1l3&k#zt7 literal 0 HcmV?d00001 diff --git a/crates/borders-desktop/icons/Square310x310Logo.png b/crates/borders-desktop/icons/Square310x310Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f9bc04839491e66c07b16ab03743c0c53b4109cc GIT binary patch literal 8591 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 JPDHLkV1lm|RXG3v literal 0 HcmV?d00001 diff --git a/crates/borders-desktop/icons/Square71x71Logo.png b/crates/borders-desktop/icons/Square71x71Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..63440d7984936a9caa89275928d8dce97e4d033b GIT binary patch literal 2011 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∓t$dRJGIadE?vc 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 Kx8Vjl{{H|h@<;Lj literal 0 HcmV?d00001 diff --git a/crates/borders-desktop/icons/icon.ico b/crates/borders-desktop/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b3636e4b22ba65db9061cd60a77b02c92022dfd6 GIT binary patch literal 86642 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 AKmY&$ literal 0 HcmV?d00001 diff --git a/crates/borders-desktop/icons/icon.png b/crates/borders-desktop/icons/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e1cd2619e0b5ec089cbba5ec7b03ddf2b1dfceb6 GIT binary patch literal 14183 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