From fc33ad440b6f519e0946e564a8f1a7fd3dc93368 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 3 Oct 2023 09:06:45 +0200
Subject: [PATCH 01/18] Bump webpki from 0.22.1 to 0.22.2 (#758)

Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.1 to
0.22.2.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/briansmith/webpki/commits">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=webpki&package-manager=cargo&previous-version=0.22.1&new-version=0.22.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/DigitalExtinction/Game/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 5950c203..7b63dc22 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6568,9 +6568,9 @@ dependencies = [
 
 [[package]]
 name = "webpki"
-version = "0.22.1"
+version = "0.22.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e"
+checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f"
 dependencies = [
  "ring",
  "untrusted",

From 443524cf70e4fca842c268219542246c539d20f6 Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Sat, 7 Oct 2023 18:55:17 +0200
Subject: [PATCH 02/18] Fix new Clippy warnings (#760)

---
 crates/controller/src/hud/minimap/fill.rs | 4 ++--
 crates/index/src/grid.rs                  | 6 +-----
 crates/map/src/content.rs                 | 4 +---
 crates/types/src/player.rs                | 4 ++--
 crates/uom/src/quantity.rs                | 6 +-----
 5 files changed, 7 insertions(+), 17 deletions(-)

diff --git a/crates/controller/src/hud/minimap/fill.rs b/crates/controller/src/hud/minimap/fill.rs
index b797dfe3..547bf788 100644
--- a/crates/controller/src/hud/minimap/fill.rs
+++ b/crates/controller/src/hud/minimap/fill.rs
@@ -139,8 +139,8 @@ fn draw_camera_system(mut drawing: DrawingParam, camera: CameraPoint) {
 /// contained by rectangle (0, 0) -> (1, 1), b) is fully contained by the
 /// original line segment.
 fn endpoints_to_line(start: Option<Vec2>, end: Option<Vec2>) -> Option<(Vec2, Vec2)> {
-    let Some(start) = start else { return None };
-    let Some(end) = end else { return None };
+    let start = start?;
+    let end = end?;
 
     let mut start: Point<f32> = start.into();
     let mut end: Point<f32> = end.into();
diff --git a/crates/index/src/grid.rs b/crates/index/src/grid.rs
index b9fcd9a5..3025ee2d 100644
--- a/crates/index/src/grid.rs
+++ b/crates/index/src/grid.rs
@@ -112,11 +112,7 @@ impl TileGrid {
     }
 
     fn insert_to_tile(&mut self, entity: Entity, tile_coords: IVec2) {
-        let inserted = self
-            .tiles
-            .entry(tile_coords)
-            .or_insert_with(AHashSet::new)
-            .insert(entity);
+        let inserted = self.tiles.entry(tile_coords).or_default().insert(entity);
         debug_assert!(inserted);
     }
 
diff --git a/crates/map/src/content.rs b/crates/map/src/content.rs
index c74a5a52..52540fe3 100644
--- a/crates/map/src/content.rs
+++ b/crates/map/src/content.rs
@@ -58,9 +58,7 @@ impl MapContent {
 
         for (i, object) in self.objects.iter().enumerate() {
             if let InnerObject::Active(object) = object.inner() {
-                let counter = counts
-                    .entry(object.player())
-                    .or_insert_with(Counter::default);
+                let counter = counts.entry(object.player()).or_default();
 
                 match object.object_type() {
                     ActiveObjectType::Building(_) => counter.buildings += 1,
diff --git a/crates/types/src/player.rs b/crates/types/src/player.rs
index 5e2fb068..2d1de10f 100644
--- a/crates/types/src/player.rs
+++ b/crates/types/src/player.rs
@@ -42,13 +42,13 @@ impl fmt::Display for Player {
 
 impl PartialOrd for Player {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.to_num().partial_cmp(&other.to_num())
+        Some(self.cmp(other))
     }
 }
 
 impl Ord for Player {
     fn cmp(&self, other: &Self) -> Ordering {
-        self.partial_cmp(other).unwrap()
+        self.to_num().partial_cmp(&other.to_num()).unwrap()
     }
 }
 
diff --git a/crates/uom/src/quantity.rs b/crates/uom/src/quantity.rs
index b62e3886..e1921b44 100644
--- a/crates/uom/src/quantity.rs
+++ b/crates/uom/src/quantity.rs
@@ -89,11 +89,7 @@ impl<const U: Unit> Eq for Quantity<U> {}
 
 impl<const U: Unit> PartialOrd for Quantity<U> {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        #[cfg(debug_assertions)]
-        panic_on_invalid(self.0);
-        #[cfg(debug_assertions)]
-        panic_on_invalid(other.0);
-        self.0.partial_cmp(&other.0)
+        Some(self.cmp(other))
     }
 }
 

From 18571f7d6a5578b4e62e6e41590a81124fdc7d6a Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Sat, 7 Oct 2023 19:04:11 +0200
Subject: [PATCH 03/18] de_net: Fix header parsing (#759)

The client and the network intermediaries must not be trusted: a
datagram with less than 4 bytes must be handled gracefully (without a
crash).

Fixes #754.
---
 crates/net/src/header.rs | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/crates/net/src/header.rs b/crates/net/src/header.rs
index 6477466e..74424e83 100644
--- a/crates/net/src/header.rs
+++ b/crates/net/src/header.rs
@@ -41,12 +41,11 @@ impl DatagramHeader {
     }
 
     /// Reads the header from the beginning of a bytes buffer.
-    ///
-    /// # Panics
-    ///
-    /// Panics if the buffer is smaller than header.
     pub(crate) fn read(data: &[u8]) -> Result<Self, HeaderError> {
-        assert!(data.len() >= 4);
+        if data.len() < 4 {
+            return Err(HeaderError::Incomplete);
+        }
+
         debug_assert!(u32::BITS == (HEADER_SIZE as u32) * 8);
 
         let mask = data[0];
@@ -202,6 +201,8 @@ impl fmt::Display for Peers {
 pub(crate) enum HeaderError {
     #[error("The header is invalid")]
     Invalid,
+    #[error("The data is too short and does not contain full header.")]
+    Incomplete,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]

From ad9cc3028d99bf006399560bf58e3f1467ba0a4a Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Sat, 7 Oct 2023 19:22:41 +0200
Subject: [PATCH 04/18] de_connector: Return non-zero exit code on failure
 (#761)

This is crutial for e.g. systemd units so that they can restart the
service on-failure.
---
 crates/connector/src/lib.rs  | 11 ++++-------
 crates/connector/src/main.rs | 12 +++++++++---
 2 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/crates/connector/src/lib.rs b/crates/connector/src/lib.rs
index 5fa2100d..5c2d7b4b 100644
--- a/crates/connector/src/lib.rs
+++ b/crates/connector/src/lib.rs
@@ -1,7 +1,7 @@
 use anyhow::Context;
 use async_std::task;
 use de_net::Socket;
-use tracing::{error, info};
+use tracing::info;
 
 use crate::server::MainServer;
 
@@ -11,14 +11,11 @@ mod server;
 
 const PORT: u16 = 8082;
 
-pub fn start() {
+pub fn start() -> Result<(), String> {
     info!("Starting...");
-
     task::block_on(task::spawn(async {
-        if let Err(error) = start_inner().await {
-            error!("{:?}", error);
-        }
-    }));
+        start_inner().await.map_err(|error| format!("{:?}", error))
+    }))
 }
 
 async fn start_inner() -> anyhow::Result<()> {
diff --git a/crates/connector/src/main.rs b/crates/connector/src/main.rs
index 9fc5f3f1..c8a27a9c 100644
--- a/crates/connector/src/main.rs
+++ b/crates/connector/src/main.rs
@@ -1,11 +1,17 @@
 use de_connector_lib::start;
-use tracing::Level;
+use tracing::{error, Level};
 use tracing_subscriber::FmtSubscriber;
 
-fn main() {
+fn main() -> Result<(), String> {
     let subscriber = FmtSubscriber::builder()
         .with_max_level(Level::TRACE)
         .finish();
     tracing::subscriber::set_global_default(subscriber).unwrap();
-    start();
+
+    let result = start();
+    if let Err(message) = result.as_ref() {
+        error!(message);
+    }
+
+    result
 }

From 7f12f796d3487e7025c89ae8963784f8b9b5ad67 Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Sat, 7 Oct 2023 20:03:37 +0200
Subject: [PATCH 05/18] CI: Fix udeps (#763)

This fixes the following error from CI:

```
error: the option `Z` is only accepted on the nightly compiler

note: selecting a toolchain with `+toolchain` arguments require a rustup proxy; see <https://rust-lang.github.io/rustup/concepts/index.html>

help: consider switching to a nightly toolchain: `rustup default nightly`

note: for more information about Rust's stability policy, see <https://doc.rust-lang.org/book/appendix-07-nightly-rust.html#unstable-features>

error: could not compile `sharded-slab` (lib)
warning: build failed, waiting for other jobs to finish...
Error: Process completed with exit code 101.
```

Relates to https://github.com/est31/cargo-udeps/issues/104 (one needs to
pass +nightly to `cargo` so that correct / necessary rustc is used).
---
 .github/workflows/test.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 8e827b3e..1b5cbc10 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -90,7 +90,6 @@ jobs:
         id: rust-cache
         with:
           path: |
-            ~/.cargo/bin/
             ~/.cargo/registry/index/
             ~/.cargo/registry/cache/
             ~/.cargo/git/db/
@@ -103,7 +102,8 @@ jobs:
           echo $UDEPS_VER
           curl -L "https://github.com/est31/cargo-udeps/releases/download/$UDEPS_VER/cargo-udeps-$UDEPS_VER-x86_64-unknown-linux-gnu.tar.gz" -o udeps.tar.gz
           tar -xzvf udeps.tar.gz
-          ./cargo-udeps-$UDEPS_VER-x86_64-unknown-linux-gnu/cargo-udeps udeps
+          cp ./cargo-udeps-$UDEPS_VER-x86_64-unknown-linux-gnu/cargo-udeps ~/.cargo/bin/
+          cargo +nightly udeps
 
   comments:
     name: Code Comments

From 7cd9780bc9c92e64a39bebb0c996beaacff5fc84 Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Sat, 7 Oct 2023 20:27:27 +0200
Subject: [PATCH 06/18] Cargo update (#762)

---
 Cargo.lock | 890 +++++++++++++++++++++++++++--------------------------
 1 file changed, 461 insertions(+), 429 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 7b63dc22..2a6cd875 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
 
 [[package]]
 name = "ab_glyph"
-version = "0.2.21"
+version = "0.2.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39"
+checksum = "b1061f3ff92c2f65800df1f12fc7b4ff44ee14783104187dd04dfee6f11b0fd2"
 dependencies = [
  "ab_glyph_rasterizer",
  "owned_ttf_parser",
@@ -90,17 +90,17 @@ dependencies = [
 
 [[package]]
 name = "actix-http"
-version = "3.3.1"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74"
+checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9"
 dependencies = [
  "actix-codec",
  "actix-rt",
  "actix-service",
  "actix-utils",
  "ahash 0.8.3",
- "base64 0.21.2",
- "bitflags 1.3.2",
+ "base64 0.21.4",
+ "bitflags 2.4.0",
  "brotli",
  "bytes",
  "bytestring",
@@ -134,7 +134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -152,9 +152,9 @@ dependencies = [
 
 [[package]]
 name = "actix-rt"
-version = "2.8.0"
+version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e"
+checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d"
 dependencies = [
  "futures-core",
  "tokio",
@@ -162,9 +162,9 @@ dependencies = [
 
 [[package]]
 name = "actix-server"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327"
+checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4"
 dependencies = [
  "actix-rt",
  "actix-service",
@@ -172,8 +172,7 @@ dependencies = [
  "futures-core",
  "futures-util",
  "mio",
- "num_cpus",
- "socket2 0.4.9",
+ "socket2 0.5.4",
  "tokio",
  "tracing",
 ]
@@ -201,9 +200,9 @@ dependencies = [
 
 [[package]]
 name = "actix-web"
-version = "4.3.1"
+version = "4.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96"
+checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9"
 dependencies = [
  "actix-codec",
  "actix-http",
@@ -214,7 +213,7 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "actix-web-codegen",
- "ahash 0.7.6",
+ "ahash 0.8.3",
  "bytes",
  "bytestring",
  "cfg-if",
@@ -223,7 +222,6 @@ dependencies = [
  "encoding_rs",
  "futures-core",
  "futures-util",
- "http",
  "itoa",
  "language-tags",
  "log",
@@ -235,32 +233,32 @@ dependencies = [
  "serde_json",
  "serde_urlencoded",
  "smallvec",
- "socket2 0.4.9",
- "time 0.3.25",
+ "socket2 0.5.4",
+ "time",
  "url",
 ]
 
 [[package]]
 name = "actix-web-codegen"
-version = "4.2.0"
+version = "4.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9"
+checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5"
 dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "actix-web-httpauth"
-version = "0.8.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991"
+checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
 dependencies = [
  "actix-utils",
  "actix-web",
- "base64 0.13.1",
+ "base64 0.21.4",
  "futures-core",
  "futures-util",
  "log",
@@ -269,9 +267,9 @@ dependencies = [
 
 [[package]]
 name = "addr2line"
-version = "0.20.0"
+version = "0.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
 dependencies = [
  "gimli",
 ]
@@ -307,9 +305,9 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "1.0.4"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
+checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
 dependencies = [
  "memchr",
 ]
@@ -410,30 +408,29 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "anstream"
-version = "0.3.2"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
 dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
  "anstyle-wincon",
  "colorchoice",
- "is-terminal",
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle"
-version = "1.0.1"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
+checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
 dependencies = [
  "utf8parse",
 ]
@@ -449,9 +446,9 @@ dependencies = [
 
 [[package]]
 name = "anstyle-wincon"
-version = "1.0.2"
+version = "3.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
+checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
 dependencies = [
  "anstyle",
  "windows-sys 0.48.0",
@@ -525,9 +522,9 @@ dependencies = [
 
 [[package]]
 name = "async-compat"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b48b4ff0c2026db683dea961cd8ea874737f56cffca86fa84415eaddc51c00d"
+checksum = "f4fa5132bc2934f31ee61b8ff6742dc9f7efdb7568b02f59cf9c7a4a0528bf67"
 dependencies = [
  "futures-core",
  "futures-io",
@@ -538,14 +535,14 @@ dependencies = [
 
 [[package]]
 name = "async-executor"
-version = "1.5.1"
+version = "1.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
+checksum = "2c1da3ae8dabd9c00f453a329dfe1fb28da3c0a72e2478cdcd93171740c20499"
 dependencies = [
  "async-lock",
  "async-task",
  "concurrent-queue",
- "fastrand 1.9.0",
+ "fastrand 2.0.1",
  "futures-lite",
  "slab",
 ]
@@ -579,7 +576,7 @@ dependencies = [
  "log",
  "parking",
  "polling",
- "rustix 0.37.23",
+ "rustix 0.37.24",
  "slab",
  "socket2 0.4.9",
  "waker-fn",
@@ -607,7 +604,7 @@ dependencies = [
  "cfg-if",
  "event-listener",
  "futures-lite",
- "rustix 0.37.23",
+ "rustix 0.37.24",
  "signal-hook",
  "windows-sys 0.48.0",
 ]
@@ -656,9 +653,9 @@ dependencies = [
 
 [[package]]
 name = "async-task"
-version = "4.4.0"
+version = "4.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
+checksum = "b9441c6b2fe128a7c2bf680a44c34d0df31ce09e5b7e401fcca3faa483dbc921"
 
 [[package]]
 name = "atoi"
@@ -686,9 +683,9 @@ dependencies = [
 
 [[package]]
 name = "atomic-waker"
-version = "1.1.1"
+version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
 
 [[package]]
 name = "autocfg"
@@ -698,9 +695,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "backtrace"
-version = "0.3.68"
+version = "0.3.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
 dependencies = [
  "addr2line",
  "cc",
@@ -719,9 +716,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64"
-version = "0.21.2"
+version = "0.21.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
+checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
 
 [[package]]
 name = "base64ct"
@@ -740,18 +737,18 @@ dependencies = [
 
 [[package]]
 name = "bevy"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18e71a9143ac21bed247c30129399af8be170309e7ff5983a1bd37e87d3da520"
+checksum = "91c6d3ec4f89e85294dc97334c5b271ddc301fdf67ac9bb994fe44d9273e6ed7"
 dependencies = [
  "bevy_internal",
 ]
 
 [[package]]
 name = "bevy_a11y"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d87d5753cefaeb5f5c5d5e937844f5386eabaf9ab95f3013e86d8fb438d424e"
+checksum = "132c9e35a77c5395951f6d25fa2c52ee92296353426df4f961e60f3ff47e2e42"
 dependencies = [
  "accesskit",
  "bevy_app",
@@ -761,9 +758,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_animation"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc510d47ec4813359b7e715edc6976380d4244833feae124977468994554a0ab"
+checksum = "f44eae3f1c35a87e38ad146f72317f19ce7616dad8bbdfb88ee752c1282d28c5"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -780,9 +777,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_app"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cb660188d5d4ceaead6d5861ce22ecedc08b68c385cc8edf0a3c0c0285560bf"
+checksum = "f557a7d59e1e16892d7544fc37316506ee598cb5310ef0365125a30783c11531"
 dependencies = [
  "bevy_derive",
  "bevy_ecs",
@@ -796,9 +793,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_asset"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6733d20a22bb10da928785fdbf6fad6cfb1c7da4a8c170ab3adbba5862c375d5"
+checksum = "9714af523da4cdf58c42a317e5ed40349708ad954a18533991fd64c8ae0a6f68"
 dependencies = [
  "anyhow",
  "async-channel",
@@ -825,9 +822,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_audio"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8f505704c3a8396c2d9dde898e19aee114143584b11bdb7189fcafc231b6e7a"
+checksum = "4de308bd63a2f7a0b77ffeb7cf00cc185ec01393c5db2091fe03964f97152749"
 dependencies = [
  "anyhow",
  "bevy_app",
@@ -845,9 +842,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_core"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1d47b435bdebeefedda95de98a419c4d3b4c160ed8ce3470ea358a16aad6038"
+checksum = "3d5272321be5fcf5ce2fb16023bc825bb10dfcb71611117296537181ce950f48"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
@@ -860,9 +857,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_core_pipeline"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a01c2652f5a6d24e0ab465e6feca8a034dfb33dfefbcdc19e436fec879a362a8"
+checksum = "67382fa9c96ce4f4e5833ed7cedd9886844a8f3284b4a717bd4ac738dcdea0c3"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -881,20 +878,20 @@ dependencies = [
 
 [[package]]
 name = "bevy_derive"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5cc78985f4d0ad1fd7b8ead06dcfaa192685775a7b1be158191c788c7d52298"
+checksum = "a44e4e2784a81430199e4157e02903a987a32127c773985506f020e7d501b62e"
 dependencies = [
  "bevy_macro_utils",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "bevy_diagnostic"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee4e366724d233fdc7e282e1415f4cd570e97fbb8443e5a8ff3f8cc5ae253ffd"
+checksum = "6babb230dc383c98fdfc9603e3a7a2a49e1e2879dbe8291059ef37dca897932e"
 dependencies = [
  "bevy_app",
  "bevy_core",
@@ -907,9 +904,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_ecs"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb6fd0ec64cd32b8fcf16157173431ba0e675b29c4643a8d6c9a9eeef6f93c32"
+checksum = "266144b36df7e834d5198049e037ecdf2a2310a76ce39ed937d1b0a6a2c4e8c6"
 dependencies = [
  "async-channel",
  "bevy_ecs_macros",
@@ -928,21 +925,21 @@ dependencies = [
 
 [[package]]
 name = "bevy_ecs_macros"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13b0fd864433db6ff825efd0eb86b2690e208151905b184cc9bfd2c3ac66c3b"
+checksum = "7157a9c3be038d5008ee3f114feb6cf6b39c1d3d32ee21a7cacb8f81fccdfa80"
 dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "bevy_encase_derive"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da8ffb3d214ee4d8b7e851bc8409768a0f18c872c3a25065c248611ff832180e"
+checksum = "d0ac0f55ad6bca1be7b0f35bbd5fc95ed3d31e4e9db158fee8e5327f59006001"
 dependencies = [
  "bevy_macro_utils",
  "encase_derive_impl",
@@ -950,9 +947,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_gilrs"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b84a2fbca3811261bcf02908132096595b81e5ec033169f922d6077f57baabb7"
+checksum = "65f4d79c55829f8016014593a42453f61a564ffb06ef79460d25696ccdfac67b"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
@@ -966,9 +963,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_gizmos"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64c08196fcb36b7175577443cbe2c1193d596a15eb0fa210bae378e6e1478876"
+checksum = "e286a3e7276431963f4aa29165ea5429fa7dbbc6d5c5ba0c531e7dd44ecc88a2"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -986,9 +983,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_gltf"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "478c832d8b132198ca9485c636c97eaea7a1fe393dabadbcabc693ef4437e0db"
+checksum = "f07494a733dca032e71a20f4b1f423de765da49cbff34406ae6cd813f9b50c41"
 dependencies = [
  "anyhow",
  "base64 0.13.1",
@@ -1017,9 +1014,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_hierarchy"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "402789ee53acf345243cf2c86a895d6cf8631e533780ed261c6ecf891eb050b8"
+checksum = "103f8f58416ac6799b8c7f0b418f1fac9eba44fa924df3b0e16b09256b897e3d"
 dependencies = [
  "bevy_app",
  "bevy_core",
@@ -1032,9 +1029,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_input"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9dfd9c768cf153f3fc807661996b2db44b824299860ba198fb3b92dd731bdd8"
+checksum = "ffbd935401101ac8003f3c3aea70788c65ad03f7a32716a10608bedda7a648bc"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
@@ -1046,9 +1043,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_internal"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1006f2c501bceb1aef5cc18ed07eb822f295763227b83ba6887e6743698af9f8"
+checksum = "e0e35a9b2bd29aa784b3cc416bcbf2a298f69f00ca51fd042ea39d9af7fad37e"
 dependencies = [
  "bevy_a11y",
  "bevy_animation",
@@ -1098,9 +1095,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_log"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0b1e5ca5b217dd384a3bf9186df0d0da757035f9f06d8eec0ebe62cffc05c34"
+checksum = "07dcc615ff4f617b06c3f9522fca3c55d56f9644db293318f8ab68fcdea5d4fe"
 dependencies = [
  "android_log-sys",
  "bevy_app",
@@ -1114,21 +1111,21 @@ dependencies = [
 
 [[package]]
 name = "bevy_macro_utils"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1cd460205fe05634d58b32d9bb752b1b4eaf32b2d29cbd4161ba35eb44a2f8c"
+checksum = "23ddc18d489b4e57832d4958cde7cd2f349f0ad91e5892ac9e2f2ee16546b981"
 dependencies = [
  "quote",
  "rustc-hash",
- "syn 2.0.29",
+ "syn 2.0.38",
  "toml_edit",
 ]
 
 [[package]]
 name = "bevy_math"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "267f2ec44aa948051768b1320c2dbff0871799e0a3b746f5fe5b6ee7258fbaf5"
+checksum = "78286a81fead796dc4b45ab14f4f02fe29a94423d3587bcfef872b2a8e0a474b"
 dependencies = [
  "glam",
  "serde",
@@ -1136,18 +1133,18 @@ dependencies = [
 
 [[package]]
 name = "bevy_mikktspace"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d98eaa7f40b97bb9b2d681ecf9fd439830a7eb88ad2846680d4f4acd285cf84"
+checksum = "6cfc2a21ea47970a9b1f0f4735af3256a8f204815bd756110051d10f9d909497"
 dependencies = [
  "glam",
 ]
 
 [[package]]
 name = "bevy_pbr"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9386944248ac8fcaaabec2302b0e7cd8196ef5d7b7a2e63b10ace3eeb813d3de"
+checksum = "63ca796a619e61cd43a0a3b11fde54644f7f0732a1fba1eef5d406248c6eba85"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -1168,15 +1165,15 @@ dependencies = [
 
 [[package]]
 name = "bevy_ptr"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15702dff420fac72c2ab92428a8600e079ae89c5845401c4e39b843665a3d2d0"
+checksum = "72c7586401a46f7d8e436028225c1df5288f2e0082d066b247a82466fea155c6"
 
 [[package]]
 name = "bevy_reflect"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ac66cccbf1457c5cfc004a0e83569bd4ddc5d6310701f4af6aa81001fe2964a"
+checksum = "0778197a1eb3e095a71417c74b7152ede02975cdc95b5ea4ddc5251ed00a2eb5"
 dependencies = [
  "bevy_math",
  "bevy_ptr",
@@ -1195,23 +1192,23 @@ dependencies = [
 
 [[package]]
 name = "bevy_reflect_derive"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5a2a1fa784e9a22560b9de350246a159cd59239eb61c7b66824031b3b28abb0"
+checksum = "342a4b2d09db22c48607d23ad59a056aff1ee004549050a51d490d375ba29528"
 dependencies = [
  "bevy_macro_utils",
  "bit-set",
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
  "uuid",
 ]
 
 [[package]]
 name = "bevy_render"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2e125de06ffaed8100623843b447fb524dc16f2a2120adce81143d7307278be"
+checksum = "39df4824b760928c27afc7b00fb649c7a63c9d76661ab014ff5c86537ee906cb"
 dependencies = [
  "anyhow",
  "async-channel",
@@ -1259,21 +1256,21 @@ dependencies = [
 
 [[package]]
 name = "bevy_render_macros"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e283eb4156285d0d4b85ce9b959d080b652165848f0b3f1a8770af6cfad41c34"
+checksum = "0bd08c740aac73363e32fb45af869b10cec65bcb76fe3e6cd0f8f7eebf4c36c9"
 dependencies = [
  "bevy_macro_utils",
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "bevy_scene"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a77172c572239e741283e585433e986dd7f1bfdd7f7489ff10933f59e93cbb04"
+checksum = "bd47e1263506153bef3a8be97fe2d856f206d315668c4f97510ca6cc181d9681"
 dependencies = [
  "anyhow",
  "bevy_app",
@@ -1293,9 +1290,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_sprite"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7811ade4df81ffa6bae0e293c42d784ad88ce84d2b10fa05801e3c368714581"
+checksum = "68a8ca824fad75c6ef74cfbbba0a4ce3ccc435fa23d6bf3f003f260548813397"
 dependencies = [
  "bevy_app",
  "bevy_asset",
@@ -1318,9 +1315,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_tasks"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9200e7b42d49c787d9a08675c425d8bd6393ba3beed2eac64be6027a44a01870"
+checksum = "c73bbb847c83990d3927005090df52f8ac49332e1643d2ad9aac3cd2974e66bf"
 dependencies = [
  "async-channel",
  "async-executor",
@@ -1332,9 +1329,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_text"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "822e2cdb1c46338b85b7648d866f0b631cab23bd8f24395bb8ee7842dde024f0"
+checksum = "692288ab7b0a9f8b38058964c52789fc6bcb63703b23de51cce90ec41bfca355"
 dependencies = [
  "ab_glyph",
  "anyhow",
@@ -1355,9 +1352,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_time"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ba50bf25c4dc40296b744f77de10d39c8981b710d8dce609da9de5e54ef164b"
+checksum = "3d58d6dbae9c8225d8c0e0f04d2c5dbb71d22adc01ecd5ab3cebc364139e4a6d"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
@@ -1369,9 +1366,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_transform"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86043ec5cc3cf406d33c0e4d6920171601e186b1288e9b4f5ae54682a0564032"
+checksum = "3b9b0ac0149a57cd846cb357a35fc99286f9848e53d4481954608ac9552ed2d4"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
@@ -1382,9 +1379,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_ui"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "105c82a79df1dfcdb75941fa1f58dbdbd8f702a740bc39e7bf719c3d51d55286"
+checksum = "59b6d295a755e5b79e869a09e087029d72974562a521ec7ccfba7141fa948a32"
 dependencies = [
  "bevy_a11y",
  "bevy_app",
@@ -1412,14 +1409,14 @@ dependencies = [
 
 [[package]]
 name = "bevy_utils"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "829eb8d0d06a0baeabc2e8bad74136ed3329b055aa1e11c5d9df09ebb9be3d85"
+checksum = "08d9484e32434ea84dc548cff246ce0c6f756c1336f5ea03f24ac120a48595c7"
 dependencies = [
  "ahash 0.8.3",
  "bevy_utils_proc_macros",
  "getrandom",
- "hashbrown 0.14.0",
+ "hashbrown 0.14.1",
  "instant",
  "petgraph",
  "thiserror",
@@ -1429,20 +1426,20 @@ dependencies = [
 
 [[package]]
 name = "bevy_utils_proc_macros"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d104f29e231123c703e8b394e2341d2425c33c5a2e9ab8cc8d0a554bdb62a41"
+checksum = "5391b242c36f556db01d5891444730c83aa9dd648b6a8fd2b755d22cb3bddb57"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "bevy_window"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a66f9963152093220fc5ffd2244cadcc7f79cf2c23dd02076f4d0cbb7f5162f"
+checksum = "bd584c0da7c4ada6557b09f57f30fb7cff21ccedc641473fc391574b4c9b7944"
 dependencies = [
  "bevy_app",
  "bevy_ecs",
@@ -1455,9 +1452,9 @@ dependencies = [
 
 [[package]]
 name = "bevy_winit"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "003e20cff652a364f1f98b0d5ba24f53140dc77643241afe4a9b848bdde66184"
+checksum = "bfdc044abdb95790c20053e6326760f0a2985f0dcd78613d397bf35f16039d53"
 dependencies = [
  "accesskit_winit",
  "approx",
@@ -1499,11 +1496,11 @@ dependencies = [
 
 [[package]]
 name = "bindgen"
-version = "0.64.0"
+version = "0.68.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
+checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.4.0",
  "cexpr",
  "clang-sys",
  "lazy_static",
@@ -1514,7 +1511,7 @@ dependencies = [
  "regex",
  "rustc-hash",
  "shlex",
- "syn 1.0.109",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -1583,24 +1580,25 @@ dependencies = [
 
 [[package]]
 name = "blocking"
-version = "1.3.1"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
+checksum = "94c4ef1f913d78636d78d538eec1f18de81e481f44b1be0a81060090530846e1"
 dependencies = [
  "async-channel",
  "async-lock",
  "async-task",
- "atomic-waker",
- "fastrand 1.9.0",
+ "fastrand 2.0.1",
+ "futures-io",
  "futures-lite",
- "log",
+ "piper",
+ "tracing",
 ]
 
 [[package]]
 name = "brotli"
-version = "3.3.4"
+version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
+checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
 dependencies = [
  "alloc-no-stdlib",
  "alloc-stdlib",
@@ -1609,9 +1607,9 @@ dependencies = [
 
 [[package]]
 name = "brotli-decompressor"
-version = "2.3.4"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
+checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448"
 dependencies = [
  "alloc-no-stdlib",
  "alloc-stdlib",
@@ -1619,52 +1617,52 @@ dependencies = [
 
 [[package]]
 name = "bstr"
-version = "1.6.0"
+version = "1.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
+checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
 dependencies = [
  "memchr",
- "regex-automata 0.3.6",
+ "regex-automata 0.3.9",
  "serde",
 ]
 
 [[package]]
 name = "bumpalo"
-version = "3.13.0"
+version = "3.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
 
 [[package]]
 name = "bytemuck"
-version = "1.13.1"
+version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
 dependencies = [
  "bytemuck_derive",
 ]
 
 [[package]]
 name = "bytemuck_derive"
-version = "1.4.1"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "byteorder"
-version = "1.4.3"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.4.0"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
 
 [[package]]
 name = "bytestring"
@@ -1720,17 +1718,16 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
 
 [[package]]
 name = "chrono"
-version = "0.4.26"
+version = "0.4.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
  "js-sys",
  "num-traits",
- "time 0.1.45",
  "wasm-bindgen",
- "winapi",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -1773,20 +1770,19 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.3.23"
+version = "4.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3"
+checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
 dependencies = [
  "clap_builder",
  "clap_derive",
- "once_cell",
 ]
 
 [[package]]
 name = "clap_builder"
-version = "4.3.23"
+version = "4.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98"
+checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
 dependencies = [
  "anstream",
  "anstyle",
@@ -1796,21 +1792,21 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.3.12"
+version = "4.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
+checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
 dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
+checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
 
 [[package]]
 name = "codespan-reporting"
@@ -1852,9 +1848,9 @@ dependencies = [
 
 [[package]]
 name = "concurrent-queue"
-version = "2.2.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
+checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -1916,7 +1912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
 dependencies = [
  "percent-encoding",
- "time 0.3.25",
+ "time",
  "version_check",
 ]
 
@@ -1979,9 +1975,9 @@ dependencies = [
 
 [[package]]
 name = "coreaudio-sys"
-version = "0.2.12"
+version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f034b2258e6c4ade2f73bf87b21047567fb913ee9550837c2316d139b0262b24"
+checksum = "d8478e5bdad14dce236b9898ea002eabfa87cbe14f0aa538dbe3b6a4bec4332d"
 dependencies = [
  "bindgen",
 ]
@@ -2269,7 +2265,7 @@ dependencies = [
  "de_net",
  "de_types",
  "futures",
- "nix 0.26.2",
+ "nix 0.26.4",
  "ntest",
  "thiserror",
  "tracing",
@@ -2833,14 +2829,14 @@ checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "encoding_rs"
-version = "0.8.32"
+version = "0.8.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
 dependencies = [
  "cfg-if",
 ]
@@ -2862,27 +2858,27 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "enum-map"
-version = "2.6.1"
+version = "2.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9705d8de4776df900a4a0b2384f8b0ab42f775e93b083b42f8ce71bdc32a47e3"
+checksum = "c188012f8542dee7b3996e44dd89461d64aa471b0a7c71a1ae2f595d259e96e5"
 dependencies = [
  "enum-map-derive",
 ]
 
 [[package]]
 name = "enum-map-derive"
-version = "0.13.0"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccb14d927583dd5c2eac0f2cf264fc4762aefe1ae14c47a8a20fc1939d3a5fc0"
+checksum = "04d0b288e3bb1d861c4403c1774a6f7a798781dfc519b3647df2a3dd4ae95f25"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -2906,18 +2902,18 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "erased-serde"
-version = "0.3.29"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc978899517288e3ebbd1a3bfc1d9537dbb87eeab149e53ea490e63bcdff561a"
+checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "errno"
-version = "0.3.2"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
+checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480"
 dependencies = [
  "errno-dragonfly",
  "libc",
@@ -2960,9 +2956,9 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "2.0.0"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
 
 [[package]]
 name = "fdeflate"
@@ -2985,6 +2981,12 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "finl_unicode"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6"
+
 [[package]]
 name = "fixedbitset"
 version = "0.4.2"
@@ -3134,7 +3136,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -3186,7 +3188,7 @@ dependencies = [
  "cfg-if",
  "js-sys",
  "libc",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi",
  "wasm-bindgen",
 ]
 
@@ -3205,9 +3207,9 @@ dependencies = [
 
 [[package]]
 name = "gilrs-core"
-version = "0.5.6"
+version = "0.5.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f85b0f27572f0560cfc4a067a2978a4a490f9fa5cf1326d30b142a288312a965"
+checksum = "5ccc99e9b8d63ffcaa334c4babfa31f46e156618a11f63efb6e8e6bcb37b830d"
 dependencies = [
  "core-foundation",
  "io-kit-sys",
@@ -3215,25 +3217,25 @@ dependencies = [
  "libc",
  "libudev-sys",
  "log",
- "nix 0.26.2",
+ "nix 0.26.4",
  "uuid",
  "vec_map",
  "wasm-bindgen",
  "web-sys",
- "windows 0.48.0",
+ "windows 0.51.1",
 ]
 
 [[package]]
 name = "gimli"
-version = "0.27.3"
+version = "0.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
 
 [[package]]
 name = "glam"
-version = "0.24.1"
+version = "0.24.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42218cb640844e3872cc3c153dc975229e080a6c4733b34709ef445610550226"
+checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"
 dependencies = [
  "bytemuck",
  "mint",
@@ -3272,9 +3274,9 @@ dependencies = [
 
 [[package]]
 name = "gltf"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4fe8d5192923fbd783c15e74627de8e27c97e1e3dec22bf54515a407249febf"
+checksum = "ad2dcfb6dd7a66f9eb3d181a29dcfb22d146b0bcdc2e1ed1713cbf03939a88ea"
 dependencies = [
  "base64 0.13.1",
  "byteorder",
@@ -3286,21 +3288,21 @@ dependencies = [
 
 [[package]]
 name = "gltf-derive"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec223c88f017861193ae128239aff8fbc4478f38a036d9d7b2ce10a52b46b1f2"
+checksum = "f2cbcea5dd47e7ad4e9ee6f040384fcd7204bbf671aa4f9e7ca7dfc9bfa1de20"
 dependencies = [
  "inflections",
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "gltf-json"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b1ba7523fcf32541f4aec96e13024c255d928eab3223f99ab945045f2a6de18"
+checksum = "7d5b810806b78dde4b71a95cc0e6fdcab34c4c617da3574df166f9987be97d03"
 dependencies = [
  "gltf-derive",
  "serde",
@@ -3353,22 +3355,22 @@ dependencies = [
 
 [[package]]
 name = "gpu-descriptor"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a"
+checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.4.0",
  "gpu-descriptor-types",
- "hashbrown 0.12.3",
+ "hashbrown 0.14.1",
 ]
 
 [[package]]
 name = "gpu-descriptor-types"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126"
+checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.4.0",
 ]
 
 [[package]]
@@ -3389,9 +3391,9 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.3.20"
+version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
+checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
 dependencies = [
  "bytes",
  "fnv",
@@ -3426,15 +3428,12 @@ name = "hashbrown"
 version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-dependencies = [
- "ahash 0.7.6",
-]
 
 [[package]]
 name = "hashbrown"
-version = "0.14.0"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
 dependencies = [
  "ahash 0.8.3",
  "allocator-api2",
@@ -3443,11 +3442,11 @@ dependencies = [
 
 [[package]]
 name = "hashlink"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f"
+checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
 dependencies = [
- "hashbrown 0.14.0",
+ "hashbrown 0.14.1",
 ]
 
 [[package]]
@@ -3489,9 +3488,9 @@ dependencies = [
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.2"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
 
 [[package]]
 name = "hex"
@@ -3661,12 +3660,12 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.0.0"
+version = "2.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
+checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
 dependencies = [
  "equivalent",
- "hashbrown 0.14.0",
+ "hashbrown 0.14.1",
 ]
 
 [[package]]
@@ -3741,7 +3740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
 dependencies = [
  "hermit-abi",
- "rustix 0.38.8",
+ "rustix 0.38.17",
  "windows-sys 0.48.0",
 ]
 
@@ -3844,7 +3843,7 @@ version = "8.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378"
 dependencies = [
- "base64 0.21.2",
+ "base64 0.21.4",
  "pem",
  "ring",
  "serde",
@@ -3874,9 +3873,9 @@ dependencies = [
 
 [[package]]
 name = "kira"
-version = "0.8.4"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39ce67549ab39d620131643a37f60b520ec4ae4450dc230f4c6296d3533272a3"
+checksum = "d15d6971aeabe05687ca909c6cf71b857e338b204c1ba9fe664e84020c633249"
 dependencies = [
  "atomic-arena",
  "cpal",
@@ -3944,9 +3943,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.147"
+version = "0.2.149"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
 
 [[package]]
 name = "libloading"
@@ -3960,9 +3959,9 @@ dependencies = [
 
 [[package]]
 name = "libloading"
-version = "0.8.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
 dependencies = [
  "cfg-if",
  "windows-sys 0.48.0",
@@ -3970,9 +3969,9 @@ dependencies = [
 
 [[package]]
 name = "libm"
-version = "0.2.7"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
 
 [[package]]
 name = "libsqlite3-sys"
@@ -4003,19 +4002,18 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.5"
+version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
+checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db"
 
 [[package]]
 name = "local-channel"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c"
+checksum = "e0a493488de5f18c8ffcba89eebb8532ffc562dc400490eb65b84893fae0b178"
 dependencies = [
  "futures-core",
  "futures-sink",
- "futures-util",
  "local-waker",
 ]
 
@@ -4073,9 +4071,9 @@ dependencies = [
 
 [[package]]
 name = "matrixmultiply"
-version = "0.3.7"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77"
+checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2"
 dependencies = [
  "autocfg",
  "rawpointer",
@@ -4083,9 +4081,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.5.0"
+version = "2.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
 
 [[package]]
 name = "memoffset"
@@ -4155,7 +4153,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
 dependencies = [
  "libc",
  "log",
- "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasi",
  "windows-sys 0.48.0",
 ]
 
@@ -4182,9 +4180,9 @@ dependencies = [
 
 [[package]]
 name = "naga_oil"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d9c27fc9c84580434af75123d13ad98d9a56e16d033b16dcfa6940728c8c225"
+checksum = "8be942a5c21c58b9b0bf4d9b99db3634ddb7a916f8e1d1d0b71820cc4150e56b"
 dependencies = [
  "bit-set",
  "codespan-reporting",
@@ -4288,16 +4286,15 @@ dependencies = [
 
 [[package]]
 name = "nix"
-version = "0.26.2"
+version = "0.26.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
 dependencies = [
  "bitflags 1.3.2",
  "cfg-if",
  "libc",
  "memoffset 0.7.1",
  "pin-utils",
- "static_assertions",
 ]
 
 [[package]]
@@ -4312,9 +4309,9 @@ dependencies = [
 
 [[package]]
 name = "notify"
-version = "6.1.0"
+version = "6.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dbf4f3b058c29e9642cf53217e0531cbfc78bc24e0a212a9837b7415b9d9007"
+checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
 dependencies = [
  "bitflags 2.4.0",
  "crossbeam-channel",
@@ -4383,9 +4380,9 @@ dependencies = [
 
 [[package]]
 name = "num-bigint"
-version = "0.4.3"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
 dependencies = [
  "autocfg",
  "num-integer",
@@ -4492,7 +4489,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -4542,9 +4539,9 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.31.1"
+version = "0.32.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
 dependencies = [
  "memchr",
 ]
@@ -4586,11 +4583,11 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
 
 [[package]]
 name = "openssl"
-version = "0.10.56"
+version = "0.10.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e"
+checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.4.0",
  "cfg-if",
  "foreign-types",
  "libc",
@@ -4607,7 +4604,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -4618,9 +4615,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.91"
+version = "0.9.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac"
+checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
 dependencies = [
  "cc",
  "libc",
@@ -4666,9 +4663,9 @@ dependencies = [
 
 [[package]]
 name = "parking"
-version = "2.1.0"
+version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
+checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067"
 
 [[package]]
 name = "parking_lot"
@@ -4812,12 +4809,12 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
 
 [[package]]
 name = "petgraph"
-version = "0.6.3"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
+checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
 dependencies = [
  "fixedbitset",
- "indexmap 1.9.3",
+ "indexmap 2.0.2",
 ]
 
 [[package]]
@@ -4837,14 +4834,14 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.12"
+version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
 
 [[package]]
 name = "pin-utils"
@@ -4852,6 +4849,17 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
+[[package]]
+name = "piper"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.0.1",
+ "futures-io",
+]
+
 [[package]]
 name = "pkg-config"
 version = "0.3.27"
@@ -4932,13 +4940,13 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
 name = "predicates"
-version = "3.0.3"
+version = "3.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9"
+checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0"
 dependencies = [
  "anstyle",
  "difflib",
- "itertools 0.10.5",
+ "itertools 0.11.0",
  "predicates-core",
 ]
 
@@ -4980,18 +4988,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "profiling"
-version = "1.0.9"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46b2164ebdb1dfeec5e337be164292351e11daf63a05174c6776b2f47460f0c9"
+checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b"
 
 [[package]]
 name = "quote"
@@ -5058,9 +5066,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
 
 [[package]]
 name = "rayon"
-version = "1.7.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
+checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
 dependencies = [
  "either",
  "rayon-core",
@@ -5068,14 +5076,12 @@ dependencies = [
 
 [[package]]
 name = "rayon-core"
-version = "1.11.0"
+version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
+checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
 dependencies = [
- "crossbeam-channel",
  "crossbeam-deque",
  "crossbeam-utils",
- "num_cpus",
 ]
 
 [[package]]
@@ -5115,14 +5121,14 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.9.3"
+version = "1.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
+checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-automata 0.3.6",
- "regex-syntax 0.7.4",
+ "regex-automata 0.3.9",
+ "regex-syntax 0.7.5",
 ]
 
 [[package]]
@@ -5136,13 +5142,13 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.3.6"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
+checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
 dependencies = [
  "aho-corasick",
  "memchr",
- "regex-syntax 0.7.4",
+ "regex-syntax 0.7.5",
 ]
 
 [[package]]
@@ -5153,9 +5159,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
 
 [[package]]
 name = "regex-syntax"
-version = "0.7.4"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
+checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
 
 [[package]]
 name = "renderdoc-sys"
@@ -5165,11 +5171,11 @@ checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"
 
 [[package]]
 name = "reqwest"
-version = "0.11.18"
+version = "0.11.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
+checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
 dependencies = [
- "base64 0.21.2",
+ "base64 0.21.4",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -5190,6 +5196,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_urlencoded",
+ "system-configuration",
  "tokio",
  "tokio-native-tls",
  "tower-service",
@@ -5245,7 +5252,7 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
 dependencies = [
- "base64 0.21.2",
+ "base64 0.21.4",
  "bitflags 2.4.0",
  "serde",
  "serde_derive",
@@ -5285,9 +5292,9 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.37.23"
+version = "0.37.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
+checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d"
 dependencies = [
  "bitflags 1.3.2",
  "errno",
@@ -5299,22 +5306,22 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.8"
+version = "0.38.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
+checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7"
 dependencies = [
  "bitflags 2.4.0",
  "errno",
  "libc",
- "linux-raw-sys 0.4.5",
+ "linux-raw-sys 0.4.8",
  "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "rustls"
-version = "0.20.8"
+version = "0.20.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
 dependencies = [
  "log",
  "ring",
@@ -5328,7 +5335,7 @@ version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
 dependencies = [
- "base64 0.21.2",
+ "base64 0.21.4",
 ]
 
 [[package]]
@@ -5416,35 +5423,35 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.18"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
+checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
 
 [[package]]
 name = "serde"
-version = "1.0.185"
+version = "1.0.188"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31"
+checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.185"
+version = "1.0.188"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec"
+checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.105"
+version = "1.0.107"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
+checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
 dependencies = [
  "itoa",
  "ryu",
@@ -5469,7 +5476,7 @@ version = "0.9.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
 dependencies = [
- "indexmap 2.0.0",
+ "indexmap 2.0.2",
  "itoa",
  "ryu",
  "serde",
@@ -5478,9 +5485,9 @@ dependencies = [
 
 [[package]]
 name = "sha1"
-version = "0.10.5"
+version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
 dependencies = [
  "cfg-if",
  "cpufeatures",
@@ -5489,9 +5496,9 @@ dependencies = [
 
 [[package]]
 name = "sha2"
-version = "0.10.7"
+version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
 dependencies = [
  "cfg-if",
  "cpufeatures",
@@ -5510,18 +5517,18 @@ dependencies = [
 
 [[package]]
 name = "sharded-slab"
-version = "0.1.4"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
 dependencies = [
  "lazy_static",
 ]
 
 [[package]]
 name = "shlex"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
+checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
 
 [[package]]
 name = "signal-hook"
@@ -5570,14 +5577,14 @@ dependencies = [
  "num-bigint",
  "num-traits",
  "thiserror",
- "time 0.3.25",
+ "time",
 ]
 
 [[package]]
 name = "slab"
-version = "0.4.8"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
 dependencies = [
  "autocfg",
 ]
@@ -5593,9 +5600,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.11.0"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
+checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
 dependencies = [
  "serde",
 ]
@@ -5621,9 +5628,9 @@ dependencies = [
 
 [[package]]
 name = "socket2"
-version = "0.5.3"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
+checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
 dependencies = [
  "libc",
  "windows-sys 0.48.0",
@@ -5668,11 +5675,11 @@ dependencies = [
 
 [[package]]
 name = "sqlformat"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e"
+checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85"
 dependencies = [
- "itertools 0.10.5",
+ "itertools 0.11.0",
  "nom",
  "unicode_categories",
 ]
@@ -5777,10 +5784,11 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
 [[package]]
 name = "stringprep"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da"
+checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6"
 dependencies = [
+ "finl_unicode",
  "unicode-bidi",
  "unicode-normalization",
 ]
@@ -5901,9 +5909,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.29"
+version = "2.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5912,9 +5920,9 @@ dependencies = [
 
 [[package]]
 name = "sysinfo"
-version = "0.29.8"
+version = "0.29.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d10ed79c22663a35a255d289a7fdcb43559fc77ff15df5ce6c341809e7867528"
+checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5"
 dependencies = [
  "cfg-if",
  "core-foundation-sys 0.8.4",
@@ -5924,11 +5932,32 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "system-configuration"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
+dependencies = [
+ "core-foundation-sys 0.8.4",
+ "libc",
+]
+
 [[package]]
 name = "taffy"
-version = "0.3.13"
+version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82870da09c57a8a5a50f830ce8993a6637b9a4932f69257f12aea3fa68f588c9"
+checksum = "798053135b826949571726ab5e30bf9509a65b3bae6425d2e2b2dd391c50a101"
 dependencies = [
  "arrayvec",
  "grid",
@@ -5943,17 +5972,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
 dependencies = [
  "cfg-if",
- "fastrand 2.0.0",
+ "fastrand 2.0.1",
  "redox_syscall 0.3.5",
- "rustix 0.38.8",
+ "rustix 0.38.17",
  "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "termcolor"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
 dependencies = [
  "winapi-util",
 ]
@@ -5966,9 +5995,9 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
 
 [[package]]
 name = "thiserror"
-version = "1.0.47"
+version = "1.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
+checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
 dependencies = [
  "thiserror-impl",
 ]
@@ -5995,13 +6024,13 @@ dependencies = [
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.47"
+version = "1.0.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
+checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -6016,20 +6045,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
-dependencies = [
- "libc",
- "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi",
-]
-
-[[package]]
-name = "time"
-version = "0.3.25"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
+checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
 dependencies = [
  "deranged",
  "itoa",
@@ -6040,15 +6058,15 @@ dependencies = [
 
 [[package]]
 name = "time-core"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
 
 [[package]]
 name = "time-macros"
-version = "0.2.11"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
+checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
 dependencies = [
  "time-core",
 ]
@@ -6092,7 +6110,7 @@ dependencies = [
  "parking_lot 0.12.1",
  "pin-project-lite",
  "signal-hook-registry",
- "socket2 0.5.3",
+ "socket2 0.5.4",
  "windows-sys 0.48.0",
 ]
 
@@ -6130,9 +6148,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.7.8"
+version = "0.7.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
+checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d"
 dependencies = [
  "bytes",
  "futures-core",
@@ -6150,11 +6168,11 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
 
 [[package]]
 name = "toml_edit"
-version = "0.19.14"
+version = "0.19.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
 dependencies = [
- "indexmap 2.0.0",
+ "indexmap 2.0.2",
  "toml_datetime",
  "winnow",
 ]
@@ -6185,7 +6203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
 dependencies = [
  "crossbeam-channel",
- "time 0.3.25",
+ "time",
  "tracing-subscriber",
 ]
 
@@ -6197,7 +6215,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -6271,9 +6289,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
 
 [[package]]
 name = "trybuild"
-version = "1.0.83"
+version = "1.0.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6df60d81823ed9c520ee897489573da4b1d79ffbe006b8134f46de1a1aa03555"
+checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1"
 dependencies = [
  "basic-toml",
  "glob",
@@ -6286,9 +6304,9 @@ dependencies = [
 
 [[package]]
 name = "ttf-parser"
-version = "0.19.1"
+version = "0.19.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a464a4b34948a5f67fddd2b823c62d9d92e44be75058b99939eae6c5b6960b33"
+checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"
 
 [[package]]
 name = "twox-hash"
@@ -6302,9 +6320,9 @@ dependencies = [
 
 [[package]]
 name = "typenum"
-version = "1.16.0"
+version = "1.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
 
 [[package]]
 name = "unicode-bidi"
@@ -6314,9 +6332,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.11"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "unicode-normalization"
@@ -6335,9 +6353,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.10"
+version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
 
 [[package]]
 name = "unicode-xid"
@@ -6365,9 +6383,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
 [[package]]
 name = "url"
-version = "2.4.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
 dependencies = [
  "form_urlencoded",
  "idna",
@@ -6444,15 +6462,15 @@ dependencies = [
 
 [[package]]
 name = "waker-fn"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
 
 [[package]]
 name = "walkdir"
-version = "2.3.3"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
 dependencies = [
  "same-file",
  "winapi-util",
@@ -6467,12 +6485,6 @@ dependencies = [
  "try-lock",
 ]
 
-[[package]]
-name = "wasi"
-version = "0.10.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
-
 [[package]]
 name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"
@@ -6500,7 +6512,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
  "wasm-bindgen-shared",
 ]
 
@@ -6534,7 +6546,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.29",
+ "syn 2.0.38",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -6655,7 +6667,7 @@ dependencies = [
  "js-sys",
  "khronos-egl",
  "libc",
- "libloading 0.8.0",
+ "libloading 0.8.1",
  "log",
  "metal",
  "naga",
@@ -6687,9 +6699,9 @@ dependencies = [
 
 [[package]]
 name = "wide"
-version = "0.7.11"
+version = "0.7.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa469ffa65ef7e0ba0f164183697b89b854253fd31aeb92358b7b6155177d62f"
+checksum = "ebecebefc38ff1860b4bc47550bbfa63af5746061cf0d29fcd7fa63171602598"
 dependencies = [
  "bytemuck",
  "safe_arch",
@@ -6719,9 +6731,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
 dependencies = [
  "winapi",
 ]
@@ -6761,6 +6773,25 @@ dependencies = [
  "windows-targets 0.48.5",
 ]
 
+[[package]]
+name = "windows"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
 [[package]]
 name = "windows-implement"
 version = "0.48.0"
@@ -6917,9 +6948,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "winit"
-version = "0.28.6"
+version = "0.28.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "866db3f712fffba75d31bf0cdecf357c8aeafd158c5b7ab51dba2a2b2d47f196"
+checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94"
 dependencies = [
  "android-activity",
  "bitflags 1.3.2",
@@ -6947,20 +6978,21 @@ dependencies = [
 
 [[package]]
 name = "winnow"
-version = "0.5.14"
+version = "0.5.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97"
+checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "winreg"
-version = "0.10.1"
+version = "0.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 dependencies = [
- "winapi",
+ "cfg-if",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -6991,9 +7023,9 @@ checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"
 
 [[package]]
 name = "xml-rs"
-version = "0.8.16"
+version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
 
 [[package]]
 name = "zstd"

From 9eef4fe5c5698caa651c6631ac02bc5d9526019b Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Wed, 11 Oct 2023 21:33:37 +0200
Subject: [PATCH 07/18] Use Polyanya for path finding (#408)

https://www.ijcai.org/proceedings/2017/0070.pdf

Fixes #407.
Fixes #406.
Fixes #168.
---
 Cargo.lock                        |   1 +
 crates/pathing/Cargo.toml         |   4 +
 crates/pathing/benches/pathing.rs |  57 +++---
 crates/pathing/src/chain.rs       |  35 +---
 crates/pathing/src/dijkstra.rs    | 270 --------------------------
 crates/pathing/src/finder.rs      |  89 +++++----
 crates/pathing/src/funnel.rs      | 312 ------------------------------
 crates/pathing/src/geometry.rs    | 185 ++++++++++++++----
 crates/pathing/src/graph.rs       | 125 +++++++-----
 crates/pathing/src/interval.rs    | 308 +++++++++++++++++++++++++++++
 crates/pathing/src/lib.rs         |   6 +-
 crates/pathing/src/node.rs        | 277 ++++++++++++++++++++++++++
 crates/pathing/src/polyanya.rs    | 183 ++++++++++++++++++
 crates/pathing/src/pplugin.rs     |  10 -
 crates/pathing/src/segmentproj.rs | 247 +++++++++++++++++++++++
 15 files changed, 1313 insertions(+), 796 deletions(-)
 delete mode 100644 crates/pathing/src/dijkstra.rs
 delete mode 100644 crates/pathing/src/funnel.rs
 create mode 100644 crates/pathing/src/interval.rs
 create mode 100644 crates/pathing/src/node.rs
 create mode 100644 crates/pathing/src/polyanya.rs
 create mode 100644 crates/pathing/src/segmentproj.rs

diff --git a/Cargo.lock b/Cargo.lock
index 2a6cd875..c1d52869 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2609,6 +2609,7 @@ dependencies = [
  "de_messages",
  "de_multiplayer",
  "de_objects",
+ "de_test_utils",
  "de_types",
  "futures-lite",
  "glam",
diff --git a/crates/pathing/Cargo.toml b/crates/pathing/Cargo.toml
index 9c8130bb..f8ce1b6c 100644
--- a/crates/pathing/Cargo.toml
+++ b/crates/pathing/Cargo.toml
@@ -33,6 +33,10 @@ spade.workspace = true
 futures-lite.workspace = true
 
 [dev-dependencies]
+# DE
+de_test_utils.workspace = true
+
+# Other
 ntest.workspace = true
 criterion.workspace = true
 
diff --git a/crates/pathing/benches/pathing.rs b/crates/pathing/benches/pathing.rs
index 25a8cbc7..213a98c8 100644
--- a/crates/pathing/benches/pathing.rs
+++ b/crates/pathing/benches/pathing.rs
@@ -1,9 +1,3 @@
-use std::{
-    fs::File,
-    io::{BufRead, BufReader},
-    path::PathBuf,
-};
-
 use bevy::prelude::Transform;
 use criterion::{
     criterion_group, criterion_main, AxisScale, BenchmarkId, Criterion, PlotConfiguration,
@@ -12,29 +6,13 @@ use criterion::{
 use de_map::size::MapBounds;
 use de_objects::Ichnography;
 use de_pathing::{create_finder, ExclusionArea, PathQueryProps, PathTarget};
+use de_test_utils::{load_points, NumPoints};
 use glam::Vec2;
 use parry2d::{math::Point, shape::ConvexPolygon};
 
-const MAP_SIZE: f32 = 8000.;
+const MAP_HALF_SIZE: f32 = 4000.;
 
-fn load_points(number: u32) -> Vec<Vec2> {
-    let mut points_path: PathBuf = env!("CARGO_MANIFEST_DIR").into();
-    points_path.push("test_data");
-    points_path.push(format!("{number}-points.txt"));
-    let reader = BufReader::new(File::open(points_path).unwrap());
-
-    let mut points = Vec::with_capacity(number as usize);
-    for line in reader.lines() {
-        let line = line.unwrap();
-        let mut numbers = line.split_whitespace();
-        let x: f32 = numbers.next().unwrap().parse().unwrap();
-        let y: f32 = numbers.next().unwrap().parse().unwrap();
-        points.push(MAP_SIZE * Vec2::new(x, y));
-    }
-    points
-}
-
-fn load_exclusions(number: u32) -> Vec<ExclusionArea> {
+fn load_exclusions(number: &NumPoints) -> Vec<ExclusionArea> {
     let ichnography = Ichnography::from(
         ConvexPolygon::from_convex_hull(&[
             Point::new(-8., 8.),
@@ -44,7 +22,8 @@ fn load_exclusions(number: u32) -> Vec<ExclusionArea> {
         ])
         .unwrap(),
     );
-    load_points(number)
+
+    load_points(number, MAP_HALF_SIZE - 20.)
         .iter()
         .map(|p| ExclusionArea::from_ichnography(&Transform::from_xyz(p.x, 0., -p.y), &ichnography))
         .collect()
@@ -55,13 +34,17 @@ fn create_finder_benchmark(c: &mut Criterion) {
     let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
     group.plot_config(plot_config);
 
-    for num_entities in [100, 1000, 10_000, 100_000] {
-        let exclusions = load_exclusions(num_entities);
+    for number in [
+        NumPoints::OneHundred,
+        NumPoints::OneThousand,
+        NumPoints::TenThousand,
+    ] {
+        let exclusions = load_exclusions(&number);
 
-        let bounds = MapBounds::new(Vec2::splat(MAP_SIZE));
+        let bounds = MapBounds::new(Vec2::splat(2. * MAP_HALF_SIZE));
 
         group.throughput(Throughput::Elements(1));
-        group.bench_function(BenchmarkId::from_parameter(num_entities), |b| {
+        group.bench_function(BenchmarkId::from_parameter(usize::from(number)), |b| {
             b.iter(|| {
                 create_finder(bounds, exclusions.clone());
             });
@@ -74,15 +57,19 @@ fn find_path_benchmark(c: &mut Criterion) {
     let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic);
     group.plot_config(plot_config);
 
-    let points = load_points(100_000);
+    let points = load_points(&NumPoints::OneHundredThousand, MAP_HALF_SIZE);
     let mut index = 0;
 
-    for num_entities in [100, 1000, 10_000, 100_000] {
-        let bounds = MapBounds::new(Vec2::splat(MAP_SIZE));
-        let finder = create_finder(bounds, load_exclusions(num_entities));
+    for number in [
+        NumPoints::OneHundred,
+        NumPoints::OneThousand,
+        NumPoints::TenThousand,
+    ] {
+        let bounds = MapBounds::new(Vec2::splat(2. * MAP_HALF_SIZE));
+        let finder = create_finder(bounds, load_exclusions(&number));
 
         group.throughput(Throughput::Elements(1));
-        group.bench_function(BenchmarkId::from_parameter(num_entities), |b| {
+        group.bench_function(BenchmarkId::from_parameter(usize::from(number)), |b| {
             b.iter(|| {
                 let start = points[index];
                 index = (index + 1) % points.len();
diff --git a/crates/pathing/src/chain.rs b/crates/pathing/src/chain.rs
index 8b03e914..e3010baa 100644
--- a/crates/pathing/src/chain.rs
+++ b/crates/pathing/src/chain.rs
@@ -6,9 +6,8 @@ use std::rc::Rc;
 use de_types::path::Path;
 use parry2d::math::Point;
 
-use crate::geometry::{which_side, Side};
-
 /// A linked list of points which keeps track of its length in meters.
+#[derive(Clone)]
 pub(crate) struct PointChain {
     prev: Option<Rc<Self>>,
     point: Point<f32>,
@@ -58,26 +57,6 @@ impl PointChain {
         self.length
     }
 
-    /// Returns true if the point has no predecessor.
-    pub(crate) fn is_first(&self) -> bool {
-        self.prev.is_none()
-    }
-
-    /// Returns relative side of a point to `self` from the perspective of the
-    /// parent point. Returns `None` if `self` has no parent.
-    ///
-    /// See [`crate::geometry::which_side`].
-    ///
-    /// # Panics
-    ///
-    /// May panic if self is a degenerate point chain or of `point` coincides
-    /// with last but one point in self.
-    pub(crate) fn which_side(&self, point: Point<f32>) -> Option<Side> {
-        self.prev
-            .as_ref()
-            .map(|p| which_side(p.point(), self.point, point))
-    }
-
     /// Returns an iterator over points in this linked list. The iterator
     /// starts at `self` and traverses all predecessors.
     pub(crate) fn iter(&self) -> Predecessors {
@@ -123,7 +102,6 @@ mod tests {
     fn test_chain() {
         let chain = PointChain::first(Point::new(1., 2.));
         assert!(chain.prev().is_none());
-        assert!(chain.is_first());
         assert_eq!(chain.point(), Point::new(1., 2.));
         assert_eq!(chain.length(), 0.);
         let collected: Vec<Point<f32>> = chain.iter().map(|p| p.point()).collect();
@@ -131,23 +109,12 @@ mod tests {
 
         let chain = PointChain::extended(&Rc::new(chain), Point::new(3., 2.));
         assert!(chain.prev().is_some());
-        assert!(!chain.is_first());
         assert_eq!(chain.point(), Point::new(3., 2.));
         assert_eq!(chain.length(), 2.);
         let collected: Vec<Point<f32>> = chain.iter().map(|p| p.point()).collect();
         assert_eq!(collected, vec![Point::new(3., 2.), Point::new(1., 2.)]);
     }
 
-    #[test]
-    fn test_which_side() {
-        let chain = PointChain::first(Point::new(1., 2.));
-        assert!(chain.which_side(Point::new(2., 1.)).is_none());
-
-        let chain = PointChain::extended(&Rc::new(chain), Point::new(3., 2.));
-        assert_eq!(chain.which_side(Point::new(2., 1.)).unwrap(), Side::Left);
-        assert_eq!(chain.which_side(Point::new(2., 3.)).unwrap(), Side::Right);
-    }
-
     #[test]
     fn test_to_path() {
         let chain = PointChain::extended(
diff --git a/crates/pathing/src/dijkstra.rs b/crates/pathing/src/dijkstra.rs
deleted file mode 100644
index 4f9067ef..00000000
--- a/crates/pathing/src/dijkstra.rs
+++ /dev/null
@@ -1,270 +0,0 @@
-//! This module contains visibility graph based path finding algorithm.
-
-use std::{cmp::Ordering, collections::BinaryHeap};
-
-use ahash::AHashSet;
-use bevy::utils::FloatOrd;
-use de_types::path::Path;
-use parry2d::{math::Point, na, query::PointQuery, shape::Segment};
-
-use crate::{
-    funnel::Funnel,
-    geometry::{orient, which_side, Side},
-    graph::VisibilityGraph,
-    PathQueryProps,
-};
-
-/// Finds and returns a reasonable path between two points.
-///
-/// Source and target points must not lie inside or on the edge of the same
-/// triangle of the triangulation from which `graph` was created.
-pub(crate) fn find_path(
-    graph: &VisibilityGraph,
-    source: PointContext,
-    target: PointContext,
-    properties: PathQueryProps,
-) -> Option<Path> {
-    let mut open_set = OpenSet::new();
-    let mut explored = AHashSet::new();
-
-    let funnel = Funnel::new(source.point());
-    for &edge_id in source.neighbours() {
-        open_set.push(Step::from_segment(
-            source.point(),
-            &funnel,
-            graph.geometry(edge_id).segment(),
-            edge_id,
-        ));
-    }
-
-    let mut sufficient: Option<Incomplete> = None;
-    while let Some(step) = open_set.pop() {
-        if !explored.insert(step.edge_id()) {
-            continue;
-        }
-
-        let geometry = graph.geometry(step.edge_id());
-        let segment = geometry.segment();
-
-        let projection = segment.project_local_point(&target.point(), true);
-        let projection_dist = na::distance(&target.point(), &projection.point);
-
-        if properties.max_distance() > 0. && projection_dist < properties.max_distance() {
-            let funnel = step.funnel().clone();
-            let incomplete = Incomplete::new(funnel, projection.point, projection_dist);
-
-            if projection_dist < properties.distance() {
-                return incomplete.closed(properties.distance());
-            }
-
-            if sufficient
-                .as_ref()
-                .map(|s| s.distance() > incomplete.distance())
-                .unwrap_or(true)
-            {
-                sufficient = Some(incomplete);
-            }
-        }
-
-        if target.has_neighbour(step.edge_id())
-            && step.side() != which_side(segment.a, segment.b, target.point())
-        {
-            return step
-                .funnel()
-                .closed(target.point())
-                .truncated(properties.distance());
-        }
-
-        for &next_edge_id in graph.neighbours(step.edge_id()) {
-            if explored.contains(&next_edge_id) {
-                continue;
-            }
-
-            let next_geom = graph.geometry(next_edge_id);
-            if step.side() == which_side(segment.a, segment.b, next_geom.midpoint()) {
-                continue;
-            }
-
-            open_set.push(Step::from_segment(
-                geometry.midpoint(),
-                step.funnel(),
-                next_geom.segment(),
-                next_edge_id,
-            ));
-        }
-    }
-
-    sufficient.and_then(|s| s.closed(properties.distance()))
-}
-
-pub(crate) struct PointContext {
-    point: Point<f32>,
-    neighbours: Vec<u32>,
-}
-
-impl PointContext {
-    /// Creates a new point context.
-    ///
-    /// # Arguments
-    ///
-    /// * `point` - position of the point in the map
-    ///
-    /// * `neighbours` - edge IDs of all neighboring edges. If the point lies
-    ///   on en edge or its end points, the edge should not be included in the
-    ///   vector.
-    pub(crate) fn new(point: Point<f32>, neighbours: Vec<u32>) -> Self {
-        Self { point, neighbours }
-    }
-
-    fn point(&self) -> Point<f32> {
-        self.point
-    }
-
-    fn neighbours(&self) -> &[u32] {
-        self.neighbours.as_slice()
-    }
-
-    fn has_neighbour(&self, edge_id: u32) -> bool {
-        self.neighbours.contains(&edge_id)
-    }
-}
-
-/// A priority queue of path exploration expansion steps.
-struct OpenSet {
-    heap: BinaryHeap<Step>,
-}
-
-impl OpenSet {
-    fn new() -> Self {
-        Self {
-            heap: BinaryHeap::new(),
-        }
-    }
-
-    fn pop(&mut self) -> Option<Step> {
-        self.heap.pop()
-    }
-
-    fn push(&mut self, step: Step) {
-        self.heap.push(step);
-    }
-}
-
-/// A path exploration step -- a jump between two neighboring triangle edges /
-/// line segments -- used in the edge/triangle graph traversal algorithm.
-struct Step {
-    score: FloatOrd,
-    /// From which side the edge was approached. This is the side from the
-    /// perspective of the edge's line segment before orientation.
-    side: Side,
-    /// Funnel expanded by all traversed edges from the starting point.
-    /// `edge_id` is the first edge not used in the funnel expansion.
-    funnel: Funnel,
-    /// Edge to be traversed.
-    edge_id: u32,
-}
-
-impl Step {
-    fn from_segment(eye: Point<f32>, funnel: &Funnel, segment: Segment, edge_id: u32) -> Self {
-        let side = which_side(segment.a, segment.b, eye);
-        let segment = orient(eye, segment);
-        let funnel = funnel.extended(segment);
-        let dist = segment.distance_to_local_point(&funnel.tail().point(), true);
-        Self::new(funnel.tail().length() + dist, side, funnel, edge_id)
-    }
-
-    fn new(score: f32, side: Side, funnel: Funnel, edge_id: u32) -> Self {
-        Self {
-            score: FloatOrd(score),
-            side,
-            funnel,
-            edge_id,
-        }
-    }
-
-    fn side(&self) -> Side {
-        self.side
-    }
-
-    fn funnel(&self) -> &Funnel {
-        &self.funnel
-    }
-
-    fn edge_id(&self) -> u32 {
-        self.edge_id
-    }
-}
-
-impl PartialEq for Step {
-    fn eq(&self, other: &Step) -> bool {
-        self.edge_id == other.edge_id && self.score == other.score
-    }
-}
-
-impl Eq for Step {}
-
-impl PartialOrd for Step {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for Step {
-    fn cmp(&self, other: &Self) -> Ordering {
-        (other.score, other.edge_id).cmp(&(self.score, self.edge_id))
-    }
-}
-
-struct Incomplete {
-    funnel: Funnel,
-    closest: Point<f32>,
-    distance: f32,
-}
-
-impl Incomplete {
-    fn new(funnel: Funnel, closest: Point<f32>, distance: f32) -> Self {
-        debug_assert!(distance >= 0.);
-        Self {
-            funnel,
-            closest,
-            distance,
-        }
-    }
-
-    fn distance(&self) -> f32 {
-        self.distance
-    }
-
-    fn closed(self, truncation: f32) -> Option<Path> {
-        let path = self.funnel.closed(self.closest);
-        let truncation = truncation - self.distance;
-        if truncation <= 0. {
-            Some(path)
-        } else {
-            path.truncated(truncation)
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_open_set() {
-        let mut set = OpenSet::new();
-        set.push(Step::new(2., Side::Left, Funnel::new(Point::origin()), 1));
-        set.push(Step::new(1.1, Side::Left, Funnel::new(Point::origin()), 2));
-        set.push(Step::new(4., Side::Left, Funnel::new(Point::origin()), 3));
-        assert_eq!(set.pop().unwrap().edge_id(), 2);
-        assert_eq!(set.pop().unwrap().edge_id(), 1);
-        assert_eq!(set.pop().unwrap().edge_id(), 3);
-    }
-
-    #[test]
-    fn test_step_ord() {
-        let step_a = Step::new(2., Side::Left, Funnel::new(Point::origin()), 1);
-        let step_b = Step::new(2.1, Side::Left, Funnel::new(Point::origin()), 2);
-        assert!(step_b < step_a);
-    }
-}
diff --git a/crates/pathing/src/finder.rs b/crates/pathing/src/finder.rs
index dc214884..627878a0 100644
--- a/crates/pathing/src/finder.rs
+++ b/crates/pathing/src/finder.rs
@@ -14,9 +14,9 @@ use rstar::{PointDistance, RTree, RTreeObject, AABB};
 use tinyvec::{ArrayVec, TinyVec};
 
 use crate::{
-    dijkstra::{find_path, PointContext},
     exclusion::ExclusionArea,
-    graph::VisibilityGraph,
+    graph::{Step, VisibilityGraph},
+    polyanya::{find_path, PointContext},
     utils::HashableSegment,
     PathTarget,
 };
@@ -67,7 +67,7 @@ impl PathFinder {
     ///   covered by `triangles`. There is no intersection between the
     ///   `exclusions` and `triangles`.
     pub(crate) fn from_triangles(
-        triangles: Vec<Triangle>,
+        mut triangles: Vec<Triangle>,
         mut exclusions: Vec<ExclusionArea>,
     ) -> Self {
         let mut graph = VisibilityGraph::new();
@@ -77,7 +77,9 @@ impl PathFinder {
             AHashMap::with_capacity(triangles.len() * 3);
         let mut tri_edge_ids = [0, 0, 0];
 
-        for triangle in triangles {
+        for (triangle_id, triangle) in triangles.drain(..).enumerate() {
+            let triangle_id: u32 = triangle_id.try_into().unwrap();
+
             let segments = triangle.edges();
             for i in 0..3 {
                 let segment = segments[i];
@@ -91,13 +93,13 @@ impl PathFinder {
                     }
                 };
             }
-            indexed_triangles.push(GraphTriangle::new(triangle, tri_edge_ids));
+            indexed_triangles.push(GraphTriangle::new(triangle, triangle_id, tri_edge_ids));
             for [edge_id, neighbour_a, neighbour_b] in [
                 [tri_edge_ids[0], tri_edge_ids[1], tri_edge_ids[2]],
                 [tri_edge_ids[1], tri_edge_ids[2], tri_edge_ids[0]],
                 [tri_edge_ids[2], tri_edge_ids[0], tri_edge_ids[1]],
             ] {
-                graph.add_neighbours(edge_id, neighbour_a, neighbour_b);
+                graph.add_neighbours(edge_id, triangle_id, neighbour_a, neighbour_b);
             }
         }
 
@@ -157,13 +159,11 @@ impl PathFinder {
             return None;
         }
 
-        if source_edges
-            .iter()
-            .filter(|s| target_edges.contains(s))
-            .take(2)
-            .count()
-            >= 2
-        {
+        if source_edges.iter().any(|s| {
+            target_edges
+                .iter()
+                .any(|t| s.triangle_id() == t.triangle_id())
+        }) {
             debug!(
                 "Trivial path from {:?} to {:?} found, trimming...",
                 from, to
@@ -192,18 +192,24 @@ impl PathFinder {
         }
     }
 
-    fn locate_triangle_edges(&self, point: Point<f32>) -> Vec<u32> {
-        self.triangles
-            .locate_all_at_point(&[point.x, point.y])
-            .flat_map(|t| t.neighbours(point))
-            .collect()
+    fn locate_triangle_edges(&self, point: Point<f32>) -> Vec<Step> {
+        let mut result = Vec::new();
+        for triangle in self.triangles.locate_all_at_point(&[point.x, point.y]) {
+            for edge_id in triangle.neighbours(point) {
+                result.push(Step::new(edge_id, triangle.triangle_id()))
+            }
+        }
+        result
     }
 
-    fn locate_exclusion_edges(&self, point: Point<f32>) -> Vec<u32> {
+    fn locate_exclusion_edges(&self, point: Point<f32>) -> Vec<Step> {
         self.exclusions
             .locate_all_at_point(&[point.x, point.y])
-            .flat_map(|t| t.neighbours())
-            .cloned()
+            .flat_map(|t| {
+                t.neighbours()
+                    .iter()
+                    .map(|&edge_id| Step::new(edge_id, u32::MAX))
+            })
             .collect()
     }
 }
@@ -211,14 +217,23 @@ impl PathFinder {
 /// A triangle used for spatial indexing inside the edge visibility graph.
 struct GraphTriangle {
     triangle: Triangle,
+    triangle_id: u32,
     /// IDs of edges of the triangle. These correspond to edges AB, BC and CA
     /// respectively.
     edges: [u32; 3],
 }
 
 impl GraphTriangle {
-    fn new(triangle: Triangle, edges: [u32; 3]) -> Self {
-        Self { triangle, edges }
+    fn new(triangle: Triangle, triangle_id: u32, edges: [u32; 3]) -> Self {
+        Self {
+            triangle,
+            triangle_id,
+            edges,
+        }
+    }
+
+    fn triangle_id(&self) -> u32 {
+        self.triangle_id
     }
 
     /// Returns (up to 3) IDs of the triangle edges excluding edges which
@@ -410,9 +425,10 @@ mod tests {
                 ),
             )
             .unwrap();
+
         assert_eq!(
             forth_path.waypoints(),
-            &[Vec2::new(18.003086, -48.81555), Vec2::new(0.2, -950.),]
+            &[Vec2::new(18.003227, -48.80841), Vec2::new(0.2, -950.),]
         );
 
         let fifth_path = finder
@@ -440,30 +456,17 @@ mod tests {
     #[timeout(100)]
     fn test_unreachable() {
         let triangles = vec![
-            Triangle::new(
-                Point::new(0., 0.),
-                Point::new(-1., 1.),
-                Point::new(-1., -1.),
-            ),
-            Triangle::new(Point::new(0., 0.), Point::new(1., 1.), Point::new(-1., 1.)),
-            Triangle::new(Point::new(0., 0.), Point::new(1., -1.), Point::new(1., 1.)),
-            Triangle::new(
-                Point::new(0., 0.),
-                Point::new(-1., -1.),
-                Point::new(1., -1.),
-            ),
-            Triangle::new(
-                Point::new(0., 30.),
-                Point::new(0., 20.),
-                Point::new(10., 20.),
-            ),
+            Triangle::new(Point::new(0., 0.), Point::new(1., 1.), Point::new(1., 0.)),
+            Triangle::new(Point::new(0., 0.), Point::new(0., 1.), Point::new(1., 1.)),
+            Triangle::new(Point::new(0., 2.), Point::new(1., 3.), Point::new(1., 2.)),
+            Triangle::new(Point::new(0., 2.), Point::new(0., 3.), Point::new(1., 3.)),
         ];
 
         let finder = PathFinder::from_triangles(triangles, vec![]);
         assert!(finder
             .find_path(
-                Point::new(-0.5, 0.),
-                PathTarget::new(Vec2::new(2., 22.), PathQueryProps::exact(), false)
+                Point::new(0.5, 2.5),
+                PathTarget::new(Vec2::new(0.5, 0.5), PathQueryProps::exact(), false)
             )
             .is_none())
     }
diff --git a/crates/pathing/src/funnel.rs b/crates/pathing/src/funnel.rs
deleted file mode 100644
index 613518cb..00000000
--- a/crates/pathing/src/funnel.rs
+++ /dev/null
@@ -1,312 +0,0 @@
-//! Implementation of funnel algorithm based on linked lists of points and
-//! incremental funnel extension API. The API makes the algorithm usable &
-//! efficient when used from graph traversal algorithms.
-
-use std::rc::Rc;
-
-use de_types::path::Path;
-use parry2d::{math::Point, shape::Segment};
-
-use crate::{chain::PointChain, geometry::Side};
-
-/// The funnel consists of a tail and left & right bounds.
-///
-/// The tail and bounds are represented by [`crate::chain::PointChain`], thus
-/// the funnel can be cheaply cloned, expanded and used in graph traversal
-/// algorithms.
-///
-/// The left and right bounds are represented by a list of points (going
-/// backwards from last funnel expansion to the tip of the tail) such that line
-/// segments in between them represent gradually closing funnel.
-///
-/// The tail is represented by a list of points where the funnel was already
-/// "closed", id est area where space between bounds narrowed to or below 0.
-///
-/// No operation on the funnel affects other funnels sharing parts of the point
-/// lists with bounds and tail.
-#[derive(Clone)]
-pub(crate) struct Funnel {
-    tail: Rc<PointChain>,
-    left: Rc<PointChain>,
-    right: Rc<PointChain>,
-}
-
-impl Funnel {
-    /// Creates a new funnel with a single point.
-    pub(crate) fn new(start: Point<f32>) -> Self {
-        Self {
-            tail: Rc::new(PointChain::first(start)),
-            left: Rc::new(PointChain::first(start)),
-            right: Rc::new(PointChain::first(start)),
-        }
-    }
-
-    /// Creates a new funnel where `a` represents side bound on `side` and `b`
-    /// represents the opposing bound.
-    ///
-    /// # Panics
-    ///
-    /// Panics if `side` is not [`Side::Left`] or [`Side::Right`].
-    fn from_sides(side: Side, tail: Rc<PointChain>, a: Rc<PointChain>, b: Rc<PointChain>) -> Self {
-        let (left, right) = match side {
-            Side::Left => (a, b),
-            Side::Right => (b, a),
-            _ => panic!("Only Left and Right sides are accepted, got: {side:?}"),
-        };
-        Self { tail, left, right }
-    }
-
-    pub(crate) fn tail(&self) -> &PointChain {
-        Rc::as_ref(&self.tail)
-    }
-
-    pub(crate) fn left(&self) -> &PointChain {
-        Rc::as_ref(&self.left)
-    }
-
-    pub(crate) fn right(&self) -> &PointChain {
-        Rc::as_ref(&self.right)
-    }
-
-    /// Returns the full shortest path inside the funnel to point `by`.
-    pub fn closed(&self, by: Point<f32>) -> Path {
-        let closed = self
-            .extended_by_point(Side::Left, by)
-            .extended_by_point(Side::Right, by);
-
-        let left_count = closed.left().iter().count();
-        let right_count = closed.right().iter().count();
-        debug_assert!(left_count <= 2);
-        debug_assert!(right_count <= 2);
-
-        // due to rounding errors, tail might not contain the very last point
-        // (i.e. `by`).
-        if (left_count + right_count) == 2 {
-            closed.tail().to_path()
-        } else {
-            PointChain::extended(&closed.tail, by).to_path()
-        }
-    }
-
-    /// Returns a new funnel which is an extension of `self` by a line segment.
-    ///
-    /// It is supposed that `by.a` is on the left side from the point of view
-    /// of the middle of the last expansion segment.
-    pub fn extended(&self, by: Segment) -> Self {
-        self.extended_by_point(Side::Left, by.a)
-            .extended_by_point(Side::Right, by.b)
-    }
-
-    /// Returns a new funnel with a side bound of the funnel extended &
-    /// modified by a point.
-    ///
-    /// In the case that the point narrows down the funnel, the operation
-    /// results in the removal of an ending portion of the side bound.
-    ///
-    /// In case that the side bound gets narrowed down beyond the opposing side
-    /// bound, the "closed" portion of the funnel is moved to the tail. See
-    /// [`close`].
-    fn extended_by_point(&self, side: Side, by: Point<f32>) -> Self {
-        let (chain, opposing) = self.sides(side);
-
-        if chain.point() == by {
-            self.clone()
-        } else if chain.which_side(by).map(|s| s == side).unwrap_or(true) {
-            self.extended_side(side, by)
-        } else {
-            let first_to_remove = chain
-                .iter()
-                .take_while(|b| b.which_side(by).map(|s| s != side).unwrap_or(false))
-                .last()
-                // At least one item was taken, otherwise previous if else
-                // would not be skipped.
-                .unwrap();
-            let last_to_keep = first_to_remove.prev().unwrap();
-            let chain = Rc::new(PointChain::extended(last_to_keep, by));
-
-            if last_to_keep.is_first() {
-                let (tail, chain, opposing) =
-                    close(side, Rc::clone(&self.tail), chain, Rc::clone(opposing));
-                Self::from_sides(side, tail, chain, opposing)
-            } else {
-                Self::from_sides(side, Rc::clone(&self.tail), chain, Rc::clone(opposing))
-            }
-        }
-    }
-
-    /// Returns a new funnel with a side bound extended by a point. The point
-    /// is simple appended on the tip of the side bound.
-    fn extended_side(&self, side: Side, by: Point<f32>) -> Self {
-        let (chain, opposing) = self.sides(side);
-        let extended = Rc::new(PointChain::extended(chain, by));
-        Self::from_sides(side, Rc::clone(&self.tail), extended, Rc::clone(opposing))
-    }
-
-    fn sides(&self, side: Side) -> (&Rc<PointChain>, &Rc<PointChain>) {
-        match side {
-            Side::Left => (&self.left, &self.right),
-            Side::Right => (&self.right, &self.left),
-            _ => panic!("Only Left and Right sides are accepted, got: {side:?}"),
-        }
-    }
-}
-
-/// Moves "closed" part of `opposing` to the tail.
-///
-/// # Arguments
-///
-/// * `side` - Side of `chain` bound.
-///
-/// * `tail` - tail to be expanded by the closed points of `chain`.
-///
-/// * `chain` - a side bound which "closes" `opposing`.
-///
-/// * `opposing` - a side bound to be closed.
-fn close(
-    side: Side,
-    tail: Rc<PointChain>,
-    chain: Rc<PointChain>,
-    opposing: Rc<PointChain>,
-) -> (Rc<PointChain>, Rc<PointChain>, Rc<PointChain>) {
-    debug_assert!(side == Side::Left || side == Side::Right);
-
-    let by = chain.point();
-    let keep = match opposing
-        .iter()
-        .position(|b| b.which_side(by).map(|s| s != side).unwrap_or(false))
-    {
-        Some(index) => index,
-        None => return (tail, chain, opposing),
-    };
-
-    let bounds: Vec<Point<f32>> = opposing.iter().map(|b| b.point()).collect();
-
-    let mut tail = tail;
-    // First item in side chains is equal to tail point. It must be skipped
-    // here.
-    for &point in bounds[keep..].iter().rev() {
-        if tail.point() != point {
-            tail = Rc::new(PointChain::extended(&tail, point));
-        }
-    }
-
-    let mut chain = Rc::new(PointChain::first(tail.point()));
-    if chain.point() != by {
-        chain = Rc::new(PointChain::extended(&chain, by));
-    }
-
-    let mut opposing = Rc::new(PointChain::first(tail.point()));
-    for &point in bounds[..keep].iter().rev() {
-        opposing = Rc::new(PointChain::extended(&opposing, point));
-    }
-
-    (tail, chain, opposing)
-}
-
-#[cfg(test)]
-mod tests {
-    use approx::assert_abs_diff_eq;
-    use glam::Vec2;
-
-    use super::*;
-
-    #[test]
-    fn test_funnel() {
-        let point_a = Point::new(1., 1.);
-        let point_b = Point::new(2., 0.);
-        let point_c = Point::new(2., 2.);
-        let point_d = Point::new(3., 1.);
-        let point_e = Point::new(2.5, 3.);
-        let point_f = Point::new(1.1, 3.);
-        let point_g = Point::new(1.7, 4.);
-        let point_h = Point::new(2., 5.);
-        let point_j = Point::new(2.5, 4.5);
-
-        let funnel = Funnel::new(point_a)
-            .extended(Segment::new(point_b, point_c))
-            .extended(Segment::new(point_d, point_c))
-            .extended(Segment::new(point_e, point_c))
-            .extended(Segment::new(point_e, point_f))
-            .extended(Segment::new(point_g, point_f))
-            .extended(Segment::new(point_g, point_h))
-            .extended(Segment::new(point_g, point_j));
-
-        let path: Vec<Point<f32>> = funnel.tail().iter().map(|p| p.point()).collect();
-        assert_abs_diff_eq!(funnel.tail().length(), 3.436, epsilon = 0.001);
-        assert_eq!(path, vec![point_g, point_c, point_a]);
-    }
-
-    #[test]
-    fn test_close() {
-        // Funnel opening to the right from the point of view of point a.
-        let point_a = Point::new(1., 2.);
-        let opposing_a = Rc::new(PointChain::first(point_a));
-        let point_b = Point::new(2., 3.);
-        let opposing_b = Rc::new(PointChain::extended(&opposing_a, point_b));
-        let point_c = Point::new(3., 5.);
-        let opposing_c = Rc::new(PointChain::extended(&opposing_b, point_c));
-        let point_d = Point::new(4., 8.);
-        let opposing_d = Rc::new(PointChain::extended(&opposing_c, point_d));
-
-        // Point is to the left from all part of the chain, should not close.
-        let (tail, chain, opposing) = close(
-            Side::Left,
-            Rc::new(PointChain::first(Point::new(1., 2.))),
-            Rc::new(PointChain::first(Point::new(5., -1.))),
-            Rc::clone(&opposing_d),
-        );
-        assert_eq!(tail.to_path().waypoints(), &[Vec2::new(1., 2.)]);
-        assert_eq!(chain.to_path().waypoints(), &[Vec2::new(5., -1.)]);
-        assert_eq!(
-            opposing.to_path().waypoints(),
-            &[
-                Vec2::new(4., 8.),
-                Vec2::new(3., 5.),
-                Vec2::new(2., 3.),
-                Vec2::new(1., 2.),
-            ]
-        );
-
-        // Point is to the right from all but last two points.
-        let (tail, chain, opposing) = close(
-            Side::Left,
-            Rc::new(PointChain::first(Point::new(1., 2.))),
-            Rc::new(PointChain::first(Point::new(5., 8.9))),
-            Rc::clone(&opposing_d),
-        );
-        assert_eq!(
-            tail.to_path().waypoints(),
-            &[Vec2::new(2., 3.), Vec2::new(1., 2.)]
-        );
-        assert_eq!(
-            chain.to_path().waypoints(),
-            &[Vec2::new(5., 8.9), Vec2::new(2., 3.)]
-        );
-        assert_eq!(
-            opposing.to_path().waypoints(),
-            &[Vec2::new(4., 8.), Vec2::new(3., 5.), Vec2::new(2., 3.)]
-        );
-
-        // Point is to the right from all points.
-        let (tail, chain, opposing) = close(
-            Side::Left,
-            Rc::new(PointChain::first(Point::new(1., 2.))),
-            Rc::new(PointChain::first(Point::new(5., 13.))),
-            Rc::clone(&opposing_d),
-        );
-        assert_eq!(
-            tail.to_path().waypoints(),
-            &[
-                Vec2::new(4., 8.),
-                Vec2::new(3., 5.),
-                Vec2::new(2., 3.),
-                Vec2::new(1., 2.),
-            ]
-        );
-        assert_eq!(
-            chain.to_path().waypoints(),
-            &[Vec2::new(5., 13.), Vec2::new(4., 8.)]
-        );
-        assert_eq!(opposing.to_path().waypoints(), &[Vec2::new(4., 8.)]);
-    }
-}
diff --git a/crates/pathing/src/geometry.rs b/crates/pathing/src/geometry.rs
index 35c9fe9b..628d8ca4 100644
--- a/crates/pathing/src/geometry.rs
+++ b/crates/pathing/src/geometry.rs
@@ -1,20 +1,6 @@
 //! Various low level geometrical operations.
 
-use parry2d::{math::Point, shape::Segment};
-
-/// Reorients the segment so that end point `a` appears on the left side of end
-/// point `b` from the perspective of `eye`.
-///
-/// # Panics
-///
-/// May panic if `eye` coincides with end points of the segment.
-pub(crate) fn orient(eye: Point<f32>, segment: Segment) -> Segment {
-    if which_side(eye, segment.a, segment.b) == Side::Left {
-        Segment::new(segment.b, segment.a)
-    } else {
-        segment
-    }
-}
+use parry2d::{math::Point, query::Ray, shape::Segment};
 
 /// Returns the side at which point `new` appears relative to point `old` from
 /// the perspective of `eye`.
@@ -24,10 +10,9 @@ pub(crate) fn orient(eye: Point<f32>, segment: Segment) -> Segment {
 ///
 /// # Panics
 ///
-/// May panic if `eye` coincides with `old` or `new`.
+/// May panic if `eye` coincides with `old`.
 pub(crate) fn which_side(eye: Point<f32>, old: Point<f32>, new: Point<f32>) -> Side {
     debug_assert!(Point::from(old - eye) != Point::origin());
-    debug_assert!(Point::from(new - eye) != Point::origin());
     let perp: f32 = (eye - old).perp(&(eye - new));
     if perp < 0. {
         Side::Left
@@ -45,33 +30,106 @@ pub(crate) enum Side {
     Right,
 }
 
+/// Projection of a ray onto a line segment.
+#[derive(Clone, Copy)]
+pub(crate) struct RayProjection {
+    parameter: Option<f32>,
+    endpoint_a_side: SimpleSide,
+}
+
+impl RayProjection {
+    pub(crate) fn calculate(ray: Ray, target: Segment) -> Self {
+        let segment_dir = target.scaled_direction();
+
+        let origin_diff = target.a - ray.origin;
+        let ray_perp_origin = ray.dir.perp(&origin_diff);
+        let ray_perp_dir = ray.dir.perp(&segment_dir);
+        let dir_perp_origin = segment_dir.perp(&origin_diff);
+
+        // This is true when the ray is parallel with the segment.
+        let is_parallel = ray_perp_dir.abs() < 0.0001;
+        // This is true when the ray points away from the line given by the
+        // segment.
+        let is_behind = dir_perp_origin * ray_perp_dir > 0.;
+
+        let parameter = if is_parallel || is_behind {
+            None
+        } else {
+            let parameter = -ray_perp_origin / ray_perp_dir;
+            if (0. ..=1.).contains(&parameter) {
+                Some(parameter)
+            } else {
+                None
+            }
+        };
+
+        let endpoint_a_side = if ray_perp_origin < 0. {
+            SimpleSide::Left
+        } else if ray_perp_origin > 0. {
+            SimpleSide::Right
+        } else if ray.dir.perp(&(target.b - ray.origin)) > 0. {
+            // When ray goes through endpoint A (or directly away from it), we
+            // pretend that the endpoint lies at the opposite site than
+            // endpoint B so that the ray "crosses" the segment.
+            SimpleSide::Left
+        } else {
+            SimpleSide::Right
+        };
+
+        Self::new(parameter, endpoint_a_side)
+    }
+
+    pub(crate) fn new(parameter: Option<f32>, endpoint_a_side: SimpleSide) -> Self {
+        #[cfg(debug_assertions)]
+        if let Some(parameter) = parameter {
+            assert!(parameter.is_finite());
+            assert!(0. <= parameter);
+            assert!(parameter <= 1.);
+        }
+        Self {
+            parameter,
+            endpoint_a_side,
+        }
+    }
+
+    /// A value between 0 and 1 (inclusive). The parameter is None if the ray
+    /// does not intersect the segment.
+    ///
+    /// The intersection point is given by `segment.a + (segment.b - segment.a)
+    /// * parameter`.
+    pub(crate) fn parameter(&self) -> Option<f32> {
+        self.parameter
+    }
+
+    /// Side of endpoint a of the segment relative to the ray.
+    ///
+    /// In the case that endpoint lies on the line given by the ray, it is
+    /// assumed that endpoint a lies on the opposite site than endpoint b.
+    pub(crate) fn endpoint_a_side(&self) -> SimpleSide {
+        self.endpoint_a_side
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub(crate) enum SimpleSide {
+    Left,
+    Right,
+}
+
+impl PartialEq<Side> for SimpleSide {
+    fn eq(&self, other: &Side) -> bool {
+        match self {
+            Self::Left => *other == Side::Left,
+            Self::Right => *other == Side::Right,
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
-    use super::*;
+    use nalgebra::Vector2;
 
-    #[test]
-    fn test_orient() {
-        let eye = Point::new(-100., -200.);
-        let segment_a = Segment::new(Point::new(1., 2.), Point::new(3., 8.));
-        let segment_b = Segment::new(Point::new(3., 8.), Point::new(1., 2.));
-
-        assert_eq!(orient(eye, segment_a), segment_a);
-        assert_eq!(orient(eye, segment_b), segment_a);
-
-        assert_eq!(
-            orient(
-                Point::new(-450.0, -950.0),
-                Segment::new(
-                    Point::new(18.612133, -18.612133),
-                    Point::new(-500.0, -1000.)
-                )
-            ),
-            Segment::new(
-                Point::new(18.612133, -18.612133),
-                Point::new(-500.0, -1000.),
-            )
-        );
-    }
+    use super::*;
 
     #[test]
     fn test_which_side() {
@@ -92,4 +150,49 @@ mod tests {
         assert_eq!(which_side(eye, a, a), Side::Straight);
         assert_eq!(which_side(Point::origin(), a, 0.5 * a), Side::Straight);
     }
+
+    #[test]
+    fn test_ray_projection() {
+        let segment = Segment::new(Point::new(3., 1.), Point::new(1., 3.));
+
+        let proj =
+            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(1., 1.)), segment);
+        assert_eq!(0.5, proj.parameter().unwrap());
+        assert_eq!(proj.endpoint_a_side(), SimpleSide::Left);
+
+        let proj =
+            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(2., 2.)), segment);
+        assert_eq!(0.5, proj.parameter().unwrap());
+        assert_eq!(proj.endpoint_a_side(), SimpleSide::Left);
+
+        let proj =
+            RayProjection::calculate(Ray::new(Point::new(2., 1.), Vector2::new(1., 0.)), segment);
+        assert_eq!(0., proj.parameter().unwrap());
+        assert_eq!(proj.endpoint_a_side(), SimpleSide::Left);
+
+        let proj = RayProjection::calculate(
+            Ray::new(Point::new(2., 1.), Vector2::new(1., -0.5)),
+            segment,
+        );
+        assert!(proj.parameter().is_none());
+        assert_eq!(proj.endpoint_a_side(), SimpleSide::Right);
+
+        let proj =
+            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(1., -1.)), segment);
+        assert!(proj.parameter().is_none());
+
+        let proj =
+            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(-1., 1.)), segment);
+        assert!(proj.parameter().is_none());
+
+        let proj =
+            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(0., -3.)), segment);
+        assert!(proj.parameter().is_none());
+        assert_eq!(proj.endpoint_a_side(), SimpleSide::Right);
+
+        let proj =
+            RayProjection::calculate(Ray::new(Point::origin(), Vector2::new(-3., 0.)), segment);
+        assert!(proj.parameter().is_none());
+        assert_eq!(proj.endpoint_a_side(), SimpleSide::Left);
+    }
 }
diff --git a/crates/pathing/src/graph.rs b/crates/pathing/src/graph.rs
index a690bf78..f0099c4c 100644
--- a/crates/pathing/src/graph.rs
+++ b/crates/pathing/src/graph.rs
@@ -1,8 +1,8 @@
 //! This module contains implementation of a edge-based visibility graph used
 //! in shortest path search on the game map.
 
-use parry2d::{math::Point, shape::Segment};
-use tinyvec::TinyVec;
+use parry2d::shape::Segment;
+use tinyvec::ArrayVec;
 
 /// Edge based visibility sub-graph.
 ///
@@ -25,22 +25,22 @@ use tinyvec::TinyVec;
 ///   fully visible one from another and not share a graph edge. However, such
 ///   triangles are always connected by a graph path.
 pub struct VisibilityGraph {
-    nodes: Vec<Node>,
+    nodes: Vec<GraphNode>,
 }
 
 impl VisibilityGraph {
     /// Returns a new empty visibility graph.
-    pub(crate) fn new() -> Self {
+    pub(super) fn new() -> Self {
         Self { nodes: Vec::new() }
     }
 
     /// Returns number of nodes in the visibility graph.
-    pub(crate) fn len(&self) -> usize {
+    pub(super) fn len(&self) -> usize {
         self.nodes.len()
     }
 
-    /// Creates a graph node without any neighbours, stores to the graph and
-    /// returns its ID.
+    /// Prepares a graph node without any neighbours, pushes it to the graph
+    /// and returns corresponding edge (node) ID.
     ///
     /// Only single node must be created when multiple triangles share an edge
     /// (have coincidental edge line segment).
@@ -48,62 +48,75 @@ impl VisibilityGraph {
     /// # Arguments
     ///
     /// * `segment` - line segment of the triangle edge.
-    pub(crate) fn new_node(&mut self, segment: Segment) -> u32 {
+    pub(super) fn new_node(&mut self, segment: Segment) -> u32 {
         let id = self.nodes.len().try_into().unwrap();
-        self.nodes.push(Node::new(EdgeGeometry::new(segment)));
+        self.nodes.push(GraphNode::new(segment));
         id
     }
 
-    /// Add 2 neighbours to a graph node (triangle edge).
+    /// Adds 2 neighbours accessible via one of the adjacent triangles to a
+    /// graph node (triangle edge).
+    ///
+    /// # Arguments
+    ///
+    /// * `edge_id` - ID of the edge whose neighbors are added.
+    ///
+    /// * `triangle_id` - ID of the traversed triangle (i.e. the triangle
+    ///   containing the source and target edges).
+    ///
+    /// * `neighbour_a_id` - edge ID of the a neighbor.
+    ///
+    /// * `neighbour_b_id` - edge ID of the a neighbor.
     ///
     /// # Panics
     ///
     /// Panics if `edge_id` already stores more than two neighbours.
-    pub(crate) fn add_neighbours(
+    pub(super) fn add_neighbours(
         &mut self,
         edge_id: u32,
+        triangle_id: u32,
         neighbour_a_id: u32,
         neighbour_b_id: u32,
     ) {
         let index: usize = edge_id.try_into().unwrap();
         let node = self.nodes.get_mut(index).unwrap();
-        node.add_neighbour(neighbour_a_id);
-        node.add_neighbour(neighbour_b_id);
+        node.add_neighbour(Step::new(neighbour_a_id, triangle_id));
+        node.add_neighbour(Step::new(neighbour_b_id, triangle_id));
     }
 
     /// Returns a geometry of a graph node (triangle edge).
-    pub(crate) fn geometry(&self, edge_id: u32) -> &EdgeGeometry {
+    pub(super) fn segment(&self, edge_id: u32) -> Segment {
         let index: usize = edge_id.try_into().unwrap();
-        self.nodes[index].geometry()
+        self.nodes[index].segment()
     }
 
     /// Returns all neighbors of a graph node (triangle edge).
-    pub(crate) fn neighbours(&self, edge_id: u32) -> &[u32] {
+    pub(super) fn neighbours(&self, edge_id: u32) -> &[Step] {
         let index: usize = edge_id.try_into().unwrap();
         self.nodes[index].neighbours()
     }
 }
 
 /// A node in the visibility graph.
-struct Node {
-    geometry: EdgeGeometry,
-    /// Neighbor IDs.
-    neighbours: TinyVec<[u32; 4]>,
+struct GraphNode {
+    segment: Segment,
+    /// Graph steps to reach direct neighbors.
+    neighbours: ArrayVec<[Step; 4]>,
 }
 
-impl Node {
-    fn new(geometry: EdgeGeometry) -> Self {
+impl GraphNode {
+    fn new(segment: Segment) -> Self {
         Self {
-            geometry,
-            neighbours: TinyVec::new(),
+            segment,
+            neighbours: ArrayVec::new(),
         }
     }
 
-    fn geometry(&self) -> &EdgeGeometry {
-        &self.geometry
+    fn segment(&self) -> Segment {
+        self.segment
     }
 
-    fn neighbours(&self) -> &[u32] {
+    fn neighbours(&self) -> &[Step] {
         self.neighbours.as_slice()
     }
 
@@ -113,37 +126,46 @@ impl Node {
     ///
     /// # Panics
     ///
-    /// Panics if the number of already stored neighbors is 4.
-    fn add_neighbour(&mut self, edge_id: u32) {
-        self.neighbours.push(edge_id);
+    /// * If the number of already stored neighbors is 4.
+    ///
+    /// * If the number of already stored triangles is 2.
+    fn add_neighbour(&mut self, step: Step) {
+        self.neighbours.push(step);
     }
 }
 
-pub(crate) struct EdgeGeometry {
-    segment: Segment,
-    /// Middle of `segment` cached for efficiency reasons.
-    midpoint: Point<f32>,
+/// A step in the triangle edge neighbor graph. Id est triangle traversal from
+/// a set of points in the triangle (one point or a line segment) to (part of)
+/// an edge of the triangle.
+#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
+pub(super) struct Step {
+    edge_id: u32,
+    triangle_id: u32,
 }
 
-impl EdgeGeometry {
-    fn new(segment: Segment) -> Self {
+impl Step {
+    pub(super) fn new(edge_id: u32, triangle_id: u32) -> Self {
         Self {
-            segment,
-            midpoint: segment.a.coords.lerp(&segment.b.coords, 0.5).into(),
+            edge_id,
+            triangle_id,
         }
     }
 
-    pub(crate) fn segment(&self) -> Segment {
-        self.segment
+    /// A target edge ID (reached from neighboring edge).
+    pub(super) fn edge_id(&self) -> u32 {
+        self.edge_id
     }
 
-    pub(crate) fn midpoint(&self) -> Point<f32> {
-        self.midpoint
+    /// ID of the traversed triangle (to reach [`Self::edge_id()`].
+    pub(super) fn triangle_id(&self) -> u32 {
+        self.triangle_id
     }
 }
 
 #[cfg(test)]
 mod tests {
+    use parry2d::math::Point;
+
     use super::*;
 
     #[test]
@@ -153,12 +175,17 @@ mod tests {
         let edge_id_a = graph.new_node(Segment::new(Point::new(1., 2.), Point::new(2., 3.)));
         let edge_id_b = graph.new_node(Segment::new(Point::new(2., 3.), Point::new(5., 6.)));
         let edge_id_c = graph.new_node(Segment::new(Point::new(5., 6.), Point::new(1., 2.)));
-        graph.add_neighbours(edge_id_a, edge_id_b, edge_id_c);
-        graph.add_neighbours(edge_id_b, edge_id_c, edge_id_a);
-
-        assert_eq!(graph.neighbours(edge_id_a), &[edge_id_b, edge_id_c]);
-        assert_eq!(graph.neighbours(edge_id_b), &[edge_id_c, edge_id_a]);
-        assert_eq!(graph.neighbours(edge_id_c), &[] as &[u32]);
-        assert_eq!(graph.geometry(edge_id_a).midpoint(), Point::new(1.5, 2.5));
+        graph.add_neighbours(edge_id_a, 1, edge_id_b, edge_id_c);
+        graph.add_neighbours(edge_id_b, 1, edge_id_c, edge_id_a);
+
+        assert_eq!(
+            graph.neighbours(edge_id_a),
+            &[Step::new(edge_id_b, 1), Step::new(edge_id_c, 1)]
+        );
+        assert_eq!(
+            graph.neighbours(edge_id_b),
+            &[Step::new(edge_id_c, 1), Step::new(edge_id_a, 1)]
+        );
+        assert_eq!(graph.neighbours(edge_id_c), &[] as &[Step]);
     }
 }
diff --git a/crates/pathing/src/interval.rs b/crates/pathing/src/interval.rs
new file mode 100644
index 00000000..b117033d
--- /dev/null
+++ b/crates/pathing/src/interval.rs
@@ -0,0 +1,308 @@
+use parry2d::{
+    math::Point,
+    query::{PointQuery, Ray, RayCast},
+    shape::Segment,
+};
+
+use crate::segmentproj::{ParamPair, SegmentOnSegmentProjection};
+
+#[derive(Clone)]
+pub(super) struct SegmentInterval {
+    segment: Segment,
+    is_a_corner: bool,
+    is_b_corner: bool,
+    edge_id: u32,
+}
+
+impl SegmentInterval {
+    /// Creates a new interval from projection parameters.
+    ///
+    /// # Arguments
+    ///
+    /// * `segment` - an original segment. Projection parameters correspond to
+    ///   this segment.
+    ///
+    /// * `projection` - parameters of endpoints a and b of the interval to be
+    ///   created.
+    ///
+    /// * `edge_id` - ID of the original edge / segment.
+    ///
+    /// # Panics
+    ///
+    /// May panic if projection parameters are not between 0 and 1 (inclusive)
+    /// or if first projection parameter is larger or equal to the second
+    /// projection parameter.
+    pub(super) fn from_projection(segment: Segment, projection: ParamPair, edge_id: u32) -> Self {
+        Self::new(
+            projection.apply(segment),
+            projection.includes_corner_a(),
+            projection.includes_corner_b(),
+            edge_id,
+        )
+    }
+
+    /// Creates a new segment interval.
+    ///
+    /// # Panics
+    ///
+    /// May panic if `segment` has zero length.
+    pub(super) fn new(
+        segment: Segment,
+        is_a_corner: bool,
+        is_b_corner: bool,
+        edge_id: u32,
+    ) -> Self {
+        debug_assert!(segment.length() > 0.);
+        Self {
+            segment,
+            is_a_corner,
+            is_b_corner,
+            edge_id,
+        }
+    }
+
+    /// Returns the corner point of the original edge (see [`Self::edge_id()`])
+    /// if it corresponds to the endpoint of `self`.
+    pub(super) fn a_corner(&self) -> Option<Point<f32>> {
+        if self.is_a_corner {
+            Some(self.segment.a)
+        } else {
+            None
+        }
+    }
+
+    /// Returns the corner point of the original edge (see [`Self::edge_id()`])
+    /// if it corresponds to the endpoint of `self`.
+    pub(super) fn b_corner(&self) -> Option<Point<f32>> {
+        if self.is_b_corner {
+            Some(self.segment.b)
+        } else {
+            None
+        }
+    }
+
+    /// Returns edge ID of the original edge.
+    pub(super) fn edge_id(&self) -> u32 {
+        self.edge_id
+    }
+
+    pub(super) fn distance_to_point(&self, point: Point<f32>) -> f32 {
+        self.segment.distance_to_local_point(&point, false)
+    }
+
+    pub(super) fn project_point(&self, point: Point<f32>) -> Point<f32> {
+        self.segment.project_local_point(&point, false).point
+    }
+
+    /// Calculates the cross point of an optimal path from a point `a` to a
+    /// point `b` via the interval.
+    pub(super) fn cross(&self, a: Point<f32>, b: Point<f32>) -> SegmentCross {
+        let ray = Ray::new(a, b - a);
+        let direct_cross = self
+            .segment
+            .cast_local_ray(&ray, 1., false)
+            .map(|param| ray.point_at(param));
+
+        match direct_cross {
+            Some(point) => SegmentCross::Direct(point),
+            None => {
+                let dist_a = (self.segment.a - a).magnitude() + (self.segment.a - b).magnitude();
+                let dist_b = (self.segment.b - a).magnitude() + (self.segment.b - b).magnitude();
+
+                if dist_a <= dist_b {
+                    SegmentCross::Corner(self.segment.a)
+                } else {
+                    SegmentCross::Corner(self.segment.b)
+                }
+            }
+        }
+    }
+
+    /// Returns projection of self onto a target segment from a given
+    /// perspective.
+    ///
+    /// # Arguments
+    ///
+    /// * `eye` - projection perspective.
+    ///
+    /// * `target` - self is projected onto this target.
+    pub(super) fn project_onto_segment(
+        &self,
+        eye: Point<f32>,
+        target: Segment,
+    ) -> SegmentOnSegmentProjection {
+        SegmentOnSegmentProjection::construct(eye, self.segment, target)
+    }
+}
+
+#[derive(Clone, Copy)]
+pub(super) enum SegmentCross {
+    /// The crossed line segment intersects with the line segment between the
+    /// points `a` and `b`.
+    Direct(Point<f32>),
+    /// The crossed line segment does not intersect with the line segment
+    /// between the points `a` and `b` and thus the optimal path traverses an
+    /// endpoint of the crossed line segment.
+    Corner(Point<f32>),
+}
+
+impl SegmentCross {
+    /// Returns the crossing point.
+    pub(super) fn point(&self) -> Point<f32> {
+        match self {
+            Self::Direct(point) => *point,
+            Self::Corner(point) => *point,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::segmentproj::ParamPair;
+
+    #[test]
+    fn test_from_projection() {
+        let interval = SegmentInterval::from_projection(
+            Segment::new(Point::new(2., 4.), Point::new(2., 0.)),
+            ParamPair::new(0.25, 0.5),
+            3,
+        );
+        assert_eq!(interval.segment.a, Point::new(2., 3.));
+        assert_eq!(interval.segment.b, Point::new(2., 2.));
+        assert_eq!(interval.a_corner(), None);
+        assert_eq!(interval.b_corner(), None);
+        assert_eq!(interval.edge_id(), 3);
+
+        let interval = SegmentInterval::from_projection(
+            Segment::new(Point::new(2., 4.), Point::new(2., 0.)),
+            ParamPair::new(0., 1.),
+            7,
+        );
+        assert_eq!(interval.segment.a, Point::new(2., 4.));
+        assert_eq!(interval.segment.b, Point::new(2., 0.));
+        assert_eq!(interval.a_corner().unwrap(), Point::new(2., 4.));
+        assert_eq!(interval.b_corner().unwrap(), Point::new(2., 0.));
+        assert_eq!(interval.edge_id(), 7);
+    }
+
+    #[test]
+    fn test_project_point() {
+        let interval = SegmentInterval::new(
+            Segment::new(Point::new(2., 4.), Point::new(2., 1.)),
+            true,
+            true,
+            0,
+        );
+        assert_eq!(
+            interval.project_point(Point::new(8., 2.)),
+            Point::new(2., 2.)
+        );
+        assert_eq!(
+            interval.project_point(Point::new(8., 10.)),
+            Point::new(2., 4.)
+        );
+    }
+
+    #[test]
+    fn test_left_corner() {
+        let a = Point::new(-1., 1.);
+        let b = Point::new(1., 1.);
+        let c = Point::new(-3., 0.);
+        let eye = Point::new(-2., 2.);
+
+        let target = Segment::new(b, c);
+
+        let parameters = [
+            ((a, b), (a, c)),
+            ((b, a), (a, c)),
+            ((a, b), (c, a)),
+            ((b, a), (c, a)),
+        ];
+        for ((aa, ab), (ba, bb)) in parameters {
+            let interval = SegmentInterval::new(Segment::new(aa, ab), true, true, 0);
+            let proj = interval.project_onto_segment(eye, Segment::new(ba, bb));
+            assert!(proj.middle().is_none());
+
+            let pair = match (proj.side_a(), proj.side_b()) {
+                (Some(pair), None) => pair,
+                (None, Some(pair)) => pair,
+                _ => unreachable!("The segment fully behind one corner."),
+            };
+
+            assert!(pair.includes_corner_a());
+            assert!(pair.includes_corner_b());
+
+            let result = pair.apply(target);
+            assert!(result.a == b || result.b == b);
+            assert!(result.a == c || result.b == c);
+        }
+    }
+
+    #[test]
+    fn test_right_corner() {
+        let a = Point::new(-1., 1.);
+        let b = Point::new(1., 1.);
+        let c = Point::new(3., 0.);
+        let eye = Point::new(2., 2.);
+
+        let target = Segment::new(b, c);
+
+        let parameters = [
+            ((a, b), (b, c)),
+            ((b, a), (b, c)),
+            ((a, b), (c, b)),
+            ((b, a), (c, b)),
+        ];
+        for ((aa, ab), (ba, bb)) in parameters {
+            let interval = SegmentInterval::new(Segment::new(aa, ab), true, true, 0);
+            let proj = interval.project_onto_segment(eye, Segment::new(ba, bb));
+            assert!(proj.middle().is_none());
+
+            let pair = match (proj.side_a(), proj.side_b()) {
+                (Some(pair), None) => pair,
+                (None, Some(pair)) => pair,
+                _ => unreachable!("The segment fully behind one corner."),
+            };
+
+            assert!(pair.includes_corner_a());
+            assert!(pair.includes_corner_b());
+
+            let result = pair.apply(target);
+            assert!(result.a == b || result.b == b);
+            assert!(result.a == c || result.b == c);
+        }
+    }
+
+    #[test]
+    fn test_eye_on_endpoint() {
+        let a = Point::new(-1., 1.);
+        let b = Point::new(1., 1.);
+        let c = Point::new(3., 0.);
+        let eye = b;
+
+        let target = Segment::new(b, c);
+
+        let parameters = [
+            ((a, b), (b, c)),
+            ((b, a), (b, c)),
+            ((a, b), (c, b)),
+            ((b, a), (c, b)),
+        ];
+        for ((aa, ab), (ba, bb)) in parameters {
+            let interval = SegmentInterval::new(Segment::new(aa, ab), true, true, 0);
+            let proj = interval.project_onto_segment(eye, Segment::new(ba, bb));
+            assert!(proj.side_a().is_none());
+            assert!(proj.side_b().is_none());
+
+            let pair = proj.middle().unwrap();
+            assert!(pair.includes_corner_a());
+            assert!(pair.includes_corner_b());
+
+            let result = pair.apply(target);
+            assert!(result.a == b || result.b == b);
+            assert!(result.a == c || result.b == c);
+        }
+    }
+}
diff --git a/crates/pathing/src/lib.rs b/crates/pathing/src/lib.rs
index 7a03814a..ce0db5f6 100644
--- a/crates/pathing/src/lib.rs
+++ b/crates/pathing/src/lib.rs
@@ -3,16 +3,18 @@
 //! game map.
 
 mod chain;
-mod dijkstra;
 mod exclusion;
 mod finder;
 mod fplugin;
-mod funnel;
 mod geometry;
 mod graph;
+mod interval;
+mod node;
 mod path;
+mod polyanya;
 mod pplugin;
 mod query;
+mod segmentproj;
 mod syncing;
 mod triangulation;
 mod utils;
diff --git a/crates/pathing/src/node.rs b/crates/pathing/src/node.rs
new file mode 100644
index 00000000..307b506b
--- /dev/null
+++ b/crates/pathing/src/node.rs
@@ -0,0 +1,277 @@
+use std::{cmp::Ordering, rc::Rc};
+
+use de_types::path::Path;
+use parry2d::{math::Point, shape::Segment};
+
+use crate::{
+    chain::PointChain,
+    graph::Step,
+    interval::{SegmentCross, SegmentInterval},
+    segmentproj::ParamPair,
+};
+
+/// Polyanya search node.
+///
+/// The node consists of a path prefix (whose last point is root point of the
+/// node), an interval (a segment or the target point) and search heuristic.
+#[derive(Clone)]
+pub(super) struct SearchNode {
+    prefix: Rc<PointChain>,
+    point_set: PointSet,
+    triangle_id: u32,
+    min_distance: f32,
+    /// Lower bound of the path length from the root via the interval the
+    /// target.
+    heuristic: f32,
+}
+
+impl SearchNode {
+    /// Creates an initial node, i.e. a node whose prefix consists of a single
+    /// point: `source`.
+    ///
+    /// # Arguments
+    ///
+    /// * `source` - starting point.
+    ///
+    /// * `target` - path finding target point.
+    ///
+    /// * `segment` - first segment to be traversed.
+    ///
+    /// * `step` - first point-to-edge step in the triangle edge neighboring
+    ///   graph.
+    pub(super) fn initial(
+        source: Point<f32>,
+        target: Point<f32>,
+        segment: Segment,
+        step: Step,
+    ) -> Self {
+        Self::from_segment_interval(
+            Rc::new(PointChain::first(source)),
+            SegmentInterval::new(segment, true, true, step.edge_id()),
+            step.triangle_id(),
+            target,
+        )
+    }
+
+    /// Creates a new Polyanya node from a path prefix and an interval. Node
+    /// heuristic is computed.
+    ///
+    /// # Arguments
+    ///
+    /// * `prefix` - node path prefix (up to the root of the node).
+    ///
+    /// * `interval` - part of a triangle edge corresponding the expansion of
+    ///   this node. I.e. set (line segment) of "furthest" explored points
+    ///   along this particular path expansion.
+    ///
+    /// * `triangle_id` - last traversed triangle (to reach `interval`).
+    ///
+    /// * `target` - searched path target.
+    fn from_segment_interval(
+        prefix: Rc<PointChain>,
+        interval: SegmentInterval,
+        triangle_id: u32,
+        target: Point<f32>,
+    ) -> Self {
+        let cross = interval.cross(prefix.point(), target).point();
+        let heuristic = (cross - prefix.point()).magnitude() + (target - cross).magnitude();
+        let min_distance = interval.distance_to_point(target);
+
+        Self {
+            prefix,
+            point_set: PointSet::Segment(interval),
+            triangle_id,
+            min_distance,
+            heuristic,
+        }
+    }
+
+    pub(super) fn root(&self) -> Point<f32> {
+        self.prefix.point()
+    }
+
+    pub(super) fn edge_id(&self) -> Option<u32> {
+        match self.point_set {
+            PointSet::Target => None,
+            PointSet::Segment(ref interval) => Some(interval.edge_id()),
+        }
+    }
+
+    pub(super) fn triangle_id(&self) -> u32 {
+        self.triangle_id
+    }
+
+    /// Returns distance of the node's interval and the target point.
+    pub(super) fn min_distance(&self) -> f32 {
+        self.min_distance
+    }
+
+    /// Constructs and returns expansion of self onto a next (adjacent) edge.
+    ///
+    /// # Arguments
+    ///
+    /// * `segment` - full line segment of the next edge.
+    ///
+    /// * `step` - single triangle traversal step.
+    ///
+    /// * `target` - path searching target point.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the last crossed triangle on the path to this node
+    /// corresponds to the triangle of the next step (i.e. if the expansion
+    /// goes backwards).
+    pub(super) fn expand_to_edge(
+        &self,
+        segment: Segment,
+        step: Step,
+        target: Point<f32>,
+    ) -> [Option<Self>; 3] {
+        assert!(step.triangle_id() != self.triangle_id);
+
+        let PointSet::Segment(ref interval) = self.point_set else {
+            panic!("Cannot expand point interval.")
+        };
+
+        let projection = interval.project_onto_segment(self.prefix.point(), segment);
+
+        let node_a = if let Some(a_corner) = interval.a_corner() {
+            projection
+                .side_a()
+                .map(|projection| self.corner(step, segment, a_corner, projection, target))
+        } else {
+            None
+        };
+
+        let node_mid = if let Some(projection) = projection.middle() {
+            let interval = SegmentInterval::from_projection(segment, projection, step.edge_id());
+            Some(Self::from_segment_interval(
+                Rc::clone(&self.prefix),
+                interval,
+                step.triangle_id(),
+                target,
+            ))
+        } else {
+            None
+        };
+
+        let node_b = if let Some(b_corner) = interval.b_corner() {
+            projection
+                .side_b()
+                .map(|projection| self.corner(step, segment, b_corner, projection, target))
+        } else {
+            None
+        };
+
+        [node_a, node_mid, node_b]
+    }
+
+    /// Creates a new node whose prefix is equal to the prefix of self with
+    /// potential addition of `corner` (as the new node root) in the case it
+    /// differs from root of self.
+    ///
+    /// # Arguments
+    ///
+    /// * `step` - the one additional step from self to reach the to be created
+    ///   node.
+    ///
+    /// * `segment` - line segment corresponding to the full edge directly
+    ///   reached (i.e. via a single triangle) from self.
+    ///
+    /// * `corner` - last path bend / corner to reach `projection` onto
+    ///   `segment`. Id est root of the to be created node.
+    ///
+    /// * `projection` - part of the target edge.
+    ///
+    /// * `target` - searched path target.
+    fn corner(
+        &self,
+        step: Step,
+        segment: Segment,
+        corner: Point<f32>,
+        projection: ParamPair,
+        target: Point<f32>,
+    ) -> Self {
+        let interval = SegmentInterval::from_projection(segment, projection, step.edge_id());
+        let prefix = if self.root() == corner {
+            Rc::clone(&self.prefix)
+        } else {
+            Rc::new(PointChain::extended(&self.prefix, corner))
+        };
+
+        Self::from_segment_interval(prefix, interval, step.triangle_id(), target)
+    }
+
+    pub(super) fn expand_to_target(&self, target: Point<f32>, triangle_id: u32) -> Option<Self> {
+        let PointSet::Segment(ref interval) = self.point_set else {
+            panic!("Cannot expand point interval.")
+        };
+
+        let prefix = match interval.cross(self.root(), target) {
+            SegmentCross::Corner(point) => Rc::new(PointChain::extended(&self.prefix, point)),
+            _ => Rc::clone(&self.prefix),
+        };
+        let heuristic = (target - prefix.point()).magnitude();
+        Some(Self {
+            prefix,
+            point_set: PointSet::Target,
+            triangle_id,
+            min_distance: 0.,
+            heuristic,
+        })
+    }
+
+    /// Constructs path from the search node.
+    ///
+    /// The resulting path is a full path from source to target if the node
+    /// (self) corresponds to the target point. Otherwise, it corresponds to
+    /// the path from source to the closest point to target in the point set of
+    /// self (on the nodes line segment).
+    pub(super) fn close(self, target: Point<f32>) -> Path {
+        let chain = match self.point_set {
+            PointSet::Target => PointChain::extended(&self.prefix, target),
+            PointSet::Segment(ref interval) => {
+                PointChain::extended(&self.prefix, interval.project_point(target))
+            }
+        };
+        chain.to_path()
+    }
+
+    pub(super) fn root_score(&self) -> f32 {
+        self.prefix.length()
+    }
+
+    fn score(&self) -> f32 {
+        self.root_score() + self.heuristic
+    }
+}
+
+impl PartialEq for SearchNode {
+    fn eq(&self, other: &Self) -> bool {
+        self.score() == other.score() && self.prefix.point() == other.prefix.point()
+    }
+}
+
+impl Eq for SearchNode {}
+
+impl PartialOrd for SearchNode {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for SearchNode {
+    fn cmp(&self, other: &Self) -> Ordering {
+        other.score().partial_cmp(&self.score()).unwrap()
+    }
+}
+
+#[derive(Clone)]
+enum PointSet {
+    /// Point set (of cardinality 1) corresponding to the path searching target
+    /// point.
+    Target,
+    /// Point set corresponding to an interval (which is either a part or a
+    /// full triangle edge).
+    Segment(SegmentInterval),
+}
diff --git a/crates/pathing/src/polyanya.rs b/crates/pathing/src/polyanya.rs
new file mode 100644
index 00000000..e9df30cd
--- /dev/null
+++ b/crates/pathing/src/polyanya.rs
@@ -0,0 +1,183 @@
+//! This module contains visibility graph based path finding algorithm.
+
+use std::collections::BinaryHeap;
+
+use ahash::AHashMap;
+use bevy::utils::FloatOrd;
+use de_types::path::Path;
+use parry2d::math::Point;
+
+use crate::{
+    graph::{Step, VisibilityGraph},
+    node::SearchNode,
+    PathQueryProps,
+};
+
+const MAX_SEARCH_STEPS: u32 = 10_000_000;
+const MAX_OPEN_SET_SIZE: usize = 1_000_000;
+
+/// Finds and returns a reasonable path between two points.
+///
+/// Source and target points must not lie inside or on the edge of the same
+/// triangle of the triangulation from which `graph` was created.
+///
+/// The path finding algorithm is based on (a modified) Polyanya:
+///
+/// Cui, M., Harabor, D. D., Grastien, A., & Data61, C. (2017, August).
+/// Compromise-free Pathfinding on a Navigation Mesh. In IJCAI (pp. 496-502).
+pub(crate) fn find_path(
+    graph: &VisibilityGraph,
+    source: PointContext,
+    target: PointContext,
+    properties: PathQueryProps,
+) -> Option<Path> {
+    let mut open_set = BinaryHeap::new();
+    let mut visited = Visited::new();
+
+    for &step in source.neighbours() {
+        open_set.push(SearchNode::initial(
+            source.point(),
+            target.point(),
+            graph.segment(step.edge_id()),
+            step,
+        ));
+    }
+
+    let Some(mut best) = open_set.peek().cloned() else {
+        return None;
+    };
+
+    let mut counter = 0;
+    while let Some(node) = open_set.pop() {
+        counter += 1;
+        if counter > MAX_SEARCH_STEPS {
+            panic!("Path finding error: reached over {MAX_SEARCH_STEPS} search steps.");
+        }
+        if open_set.len() > MAX_OPEN_SET_SIZE {
+            panic!("Path finding error: exploration open set is larger than {MAX_OPEN_SET_SIZE} nodes.");
+        }
+
+        let Some(edge_id) = node.edge_id() else {
+            best = node.clone();
+            break;
+        };
+
+        let worse = visited.test_push(node.root(), node.root_score());
+        if worse {
+            continue;
+        }
+
+        if best.min_distance() > node.min_distance() {
+            best = node.clone();
+        }
+
+        if let Some(target_step) = target
+            .neighbours()
+            .iter()
+            .find(|step| step.edge_id() == edge_id)
+        {
+            if let Some(expansion) =
+                node.expand_to_target(target.point(), target_step.triangle_id())
+            {
+                open_set.push(expansion);
+            }
+            continue;
+        }
+
+        for &step in graph.neighbours(edge_id) {
+            if step.triangle_id() == node.triangle_id() {
+                // Allow only path forward (not backward through the just
+                // traversed triangle).
+                continue;
+            }
+
+            let next_segment = graph.segment(step.edge_id());
+            for expansion in node
+                .expand_to_edge(next_segment, step, target.point())
+                .into_iter()
+                .flatten()
+            {
+                open_set.push(expansion);
+            }
+        }
+    }
+
+    let path = best.close(target.point());
+    let dist_to_target = path.waypoints()[0].distance(target.point().into());
+    if dist_to_target > properties.max_distance() {
+        None
+    } else if dist_to_target < properties.distance() {
+        path.truncated(properties.distance() - dist_to_target)
+    } else {
+        Some(path)
+    }
+}
+
+pub(super) struct PointContext {
+    point: Point<f32>,
+    neighbours: Vec<Step>,
+}
+
+impl PointContext {
+    /// Creates a new point context.
+    ///
+    /// # Arguments
+    ///
+    /// * `point` - position of the point in the map
+    ///
+    /// * `neighbours` - steps to all neighboring edges. If the point lies
+    ///   on an edge or its end points, the edge should not be included in the
+    ///   vector.
+    pub(super) fn new(point: Point<f32>, neighbours: Vec<Step>) -> Self {
+        Self { point, neighbours }
+    }
+
+    fn point(&self) -> Point<f32> {
+        self.point
+    }
+
+    fn neighbours(&self) -> &[Step] {
+        self.neighbours.as_slice()
+    }
+}
+
+struct Visited(AHashMap<(FloatOrd, FloatOrd), f32>);
+
+impl Visited {
+    fn new() -> Self {
+        Self(AHashMap::new())
+    }
+
+    /// Marks a point as visited and stores/updates its associated score.
+    ///
+    /// Returns true when the point was already visited and the previous score
+    /// was smaller than the new score.
+    fn test_push(&mut self, point: Point<f32>, score: f32) -> bool {
+        let key = (FloatOrd(point.x), FloatOrd(point.y));
+        let current_score = self.0.get(&key).cloned().unwrap_or(f32::INFINITY);
+        if current_score > score {
+            self.0.insert(key, score);
+            false
+        } else {
+            current_score != score
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_visited() {
+        let mut visited = Visited::new();
+
+        assert!(!visited.test_push(Point::new(1., 2.), 8.));
+        assert!(!visited.test_push(Point::new(1., 2.), 7.));
+        assert!(visited.test_push(Point::new(1., 2.), 7.5));
+
+        assert!(!visited.test_push(Point::new(3., 2.), 11.));
+        assert!(visited.test_push(Point::new(3., 2.), 12.));
+        assert!(!visited.test_push(Point::new(3., 2.), 7.));
+    }
+}
diff --git a/crates/pathing/src/pplugin.rs b/crates/pathing/src/pplugin.rs
index 08304480..c8b8344d 100644
--- a/crates/pathing/src/pplugin.rs
+++ b/crates/pathing/src/pplugin.rs
@@ -23,16 +23,6 @@ const TARGET_TOLERANCE: f32 = 2.;
 
 /// This plugin handles path finding requests and keeps scheduled paths
 /// up-to-date.
-///
-/// # Path Search
-///
-/// * Neighboring nodes (triangle edges) to the starting and target points are
-///   found. See [`crate::finder`].
-///
-/// * Visibility graph is traversed with a modified Dijkstra's algorithm. See
-///   [`crate::dijkstra`]. Funnel algorithm is embedded into the algorithm so
-///   path funneling can be gradually applied during the graph traversal. See
-///   [`crate::funnel`].
 pub struct PathingPlugin;
 
 impl Plugin for PathingPlugin {
diff --git a/crates/pathing/src/segmentproj.rs b/crates/pathing/src/segmentproj.rs
new file mode 100644
index 00000000..f04bb075
--- /dev/null
+++ b/crates/pathing/src/segmentproj.rs
@@ -0,0 +1,247 @@
+use parry2d::{math::Point, query::Ray, shape::Segment};
+
+use crate::geometry::{which_side, RayProjection, Side};
+
+/// Projection of a line segment onto another line segment from the perspective
+/// of an eye (a point). The projection can be looked at as a shadow cast by
+/// the one segment onto the other segment with the source of light placed at
+/// the eye.
+pub(super) struct SegmentOnSegmentProjection {
+    side_a: Option<ParamPair>,
+    middle: Option<ParamPair>,
+    side_b: Option<ParamPair>,
+}
+
+impl SegmentOnSegmentProjection {
+    pub(super) fn construct(eye: Point<f32>, source: Segment, target: Segment) -> Self {
+        let target_length = target.length();
+
+        if eye == source.a || eye == source.b {
+            return Self::new(None, Some(ParamPair::new(0., 1.)), None);
+        }
+
+        let ray_a = Ray::new(eye, source.a - eye);
+        let ray_b = Ray::new(eye, source.b - eye);
+        debug_assert_eq!(ray_a.origin, ray_b.origin);
+
+        let ray_a_proj = RayProjection::calculate(ray_a, target);
+        let ray_b_proj = RayProjection::calculate(ray_b, target);
+        let ray_b_side = which_side(ray_a.origin, ray_a.point_at(1.), ray_b.point_at(1.));
+
+        let side_a = Self::construct_a(ray_a_proj, ray_b_side, target_length);
+        let middle = Self::construct_middle(ray_a_proj, ray_b_proj, target_length);
+        let side_b = Self::construct_b(ray_b_proj, ray_b_side, target_length);
+
+        Self::new(side_a, middle, side_b)
+    }
+
+    fn construct_a(
+        ray_a_proj: RayProjection,
+        ray_b_side: Side,
+        target_length: f32,
+    ) -> Option<ParamPair> {
+        let param = ray_a_proj.parameter().unwrap_or(1.);
+        let corner = if ray_a_proj.endpoint_a_side() == ray_b_side {
+            1.
+        } else {
+            0.
+        };
+
+        ParamPair::normalized(param, corner, target_length)
+    }
+
+    fn construct_b(
+        ray_b_proj: RayProjection,
+        ray_b_side: Side,
+        target_length: f32,
+    ) -> Option<ParamPair> {
+        let param = ray_b_proj.parameter().unwrap_or(1.);
+        let corner = if ray_b_proj.endpoint_a_side() != ray_b_side {
+            1.
+        } else {
+            0.
+        };
+
+        ParamPair::normalized(param, corner, target_length)
+    }
+
+    fn construct_middle(
+        ray_a_proj: RayProjection,
+        ray_b_proj: RayProjection,
+        target_length: f32,
+    ) -> Option<ParamPair> {
+        match (ray_a_proj.parameter(), ray_b_proj.parameter()) {
+            (Some(a), Some(b)) => ParamPair::normalized(a, b, target_length),
+            (None, None) => {
+                if ray_a_proj.endpoint_a_side() == ray_b_proj.endpoint_a_side() {
+                    None
+                } else {
+                    Some(ParamPair::new(0., 1.))
+                }
+            }
+            (Some(param), None) | (None, Some(param)) => {
+                let corner = if ray_a_proj.endpoint_a_side() == ray_b_proj.endpoint_a_side() {
+                    1.
+                } else {
+                    0.
+                };
+                ParamPair::normalized(param, corner, target_length)
+            }
+        }
+    }
+
+    fn new(
+        side_a: Option<ParamPair>,
+        middle: Option<ParamPair>,
+        side_b: Option<ParamPair>,
+    ) -> Self {
+        assert!(side_a.is_some() || middle.is_some() || side_b.is_some());
+        Self {
+            side_a,
+            middle,
+            side_b,
+        }
+    }
+
+    /// Non-visible part of the target line segment adjacent to endpoint a.
+    /// This is None when all of target is visible.
+    pub(super) fn side_a(&self) -> Option<ParamPair> {
+        self.side_a
+    }
+
+    /// Visible part of the target line segment. This is None in None if no
+    /// point of the target line segment is visible (from eye via the source
+    /// line segment).
+    pub(super) fn middle(&self) -> Option<ParamPair> {
+        self.middle
+    }
+
+    /// Non-visible part of the target line segment adjacent to endpoint b.
+    /// This is None when all of target is visible.
+    pub(super) fn side_b(&self) -> Option<ParamPair> {
+        self.side_b
+    }
+}
+
+/// Parameters of a (sub-)segment of a line segment.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub(super) struct ParamPair(f32, f32);
+
+impl ParamPair {
+    /// Round parameters very close to 0 or 1 to exact 0 or 1.
+    ///
+    /// # Arguments
+    ///
+    /// * `parameter` - parameter to be rounded.
+    ///
+    /// * `scale` - size of the line segment to take into account for the
+    ///   rounding. The large the scale the less aggressive the rounding.
+    fn round(parameter: f32, scale: f32) -> f32 {
+        // Due to the nature of the algorithm, the ray and the segment
+        // frequently intersect near one of the endpoints. To avoid rounding
+        // issues, this rounding method must be used.
+
+        let scaled = parameter * scale;
+        if scaled.abs() < 0.01 {
+            0.
+        } else if (scale - scaled).abs() < 0.01 {
+            1.
+        } else {
+            parameter
+        }
+    }
+
+    /// Creates a normalized (sub-)segment parameter pair. The resulting pair
+    /// is ordered (i.e. ordering of the first two arguments does not matter)
+    /// and rounded (to avoid precision issues).
+    ///
+    /// None is returned in the case when the resulting interval contains only
+    /// a single point.
+    ///
+    /// # Arguments
+    ///
+    /// * `a` - first projection parameter. A number between 0. and 1.
+    ///   Arguments `a` and `b` may be swapped.
+    ///
+    /// * `b` - see `a`.
+    ///
+    /// * `scale` - size of the corresponding line segment. It is used for
+    ///   proper parameter rounding.
+    fn normalized(a: f32, b: f32, scale: f32) -> Option<Self> {
+        let a = Self::round(a, scale);
+        let b = Self::round(b, scale);
+
+        if a < b {
+            Some(Self::new(a, b))
+        } else if a > b {
+            Some(Self::new(b, a))
+        } else {
+            None
+        }
+    }
+
+    pub(super) fn new(a: f32, b: f32) -> Self {
+        debug_assert!(0. <= a);
+        debug_assert!(a < b);
+        debug_assert!(b <= 1.);
+        Self(a, b)
+    }
+
+    /// Apply the parameters on the parent line segment and return the
+    /// (sub-)segment.
+    pub(super) fn apply(&self, segment: Segment) -> Segment {
+        debug_assert!(segment.length() > 0.);
+        let dir = segment.scaled_direction();
+        Segment::new(
+            if self.0 == 0. {
+                // To avoid rounding errors around corners.
+                segment.a
+            } else {
+                segment.a + self.0 * dir
+            },
+            if self.1 == 1. {
+                segment.b
+            } else {
+                segment.a + self.1 * dir
+            },
+        )
+    }
+
+    /// Returns true if the first parameter coincides with endpoint a of the
+    /// parent line segment.
+    pub(super) fn includes_corner_a(&self) -> bool {
+        self.0 == 0.
+    }
+
+    /// Returns true if the first parameter coincides with endpoint b of the
+    /// parent line segment.
+    pub(super) fn includes_corner_b(&self) -> bool {
+        self.1 == 1.
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_construct() {
+        let projection = SegmentOnSegmentProjection::construct(
+            Point::new(0., 4.),
+            Segment::new(Point::new(2., 4.), Point::new(2., 1.)),
+            Segment::new(Point::new(4., 2.), Point::new(4., 10.)),
+        );
+        assert_eq!(projection.side_a(), Some(ParamPair::new(0.25, 1.)));
+        assert_eq!(projection.middle(), Some(ParamPair::new(0., 0.25)));
+        assert!(projection.side_b().is_none());
+
+        let projection = SegmentOnSegmentProjection::construct(
+            Point::new(0., 4.),
+            Segment::new(Point::new(2., 4.), Point::new(2., 1.)),
+            Segment::new(Point::new(4., 10.), Point::new(4., 2.)),
+        );
+        assert_eq!(projection.side_a(), Some(ParamPair::new(0., 0.75)));
+        assert_eq!(projection.middle(), Some(ParamPair::new(0.75, 1.)));
+        assert!(projection.side_b().is_none());
+    }
+}

From 72681ccfe84e828a65f58324297d8e6f55ad772d Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Wed, 11 Oct 2023 22:40:26 +0200
Subject: [PATCH 08/18] Multiplayer: Fix entity de-registering (#765)

This fixes: thread 'Compute Task Pool (4)' panicked at 'called
`Option::unwrap()` on a `None` value',
crates/multiplayer/src/playermsg.rs:270:78

Which was due to receiving a deregister message after the player left
the game. This can happen as a race-condition just after game end or if
the other player is kicked out due to network issues (but sends a
message around the time of the kick).

Fixes #757.
---
 crates/multiplayer/src/playermsg.rs | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/crates/multiplayer/src/playermsg.rs b/crates/multiplayer/src/playermsg.rs
index a6827f98..3a839682 100644
--- a/crates/multiplayer/src/playermsg.rs
+++ b/crates/multiplayer/src/playermsg.rs
@@ -209,7 +209,7 @@ impl<'w> NetEntityCommands<'w> {
         self.map.register(remote, local)
     }
 
-    fn deregister(&mut self, remote: EntityNet) -> Entity {
+    fn deregister(&mut self, remote: EntityNet) -> Option<Entity> {
         self.map.deregister(remote)
     }
 
@@ -261,16 +261,18 @@ impl EntityIdMapRes {
 
     /// De-registers an existing remote entity.
     ///
+    /// Returns None if the player does no longer exist.
+    ///
     /// See [`Self::register`].
     ///
     /// # Panics
     ///
     /// Panics if the entity is not registered.
-    fn deregister(&mut self, remote: EntityNet) -> Entity {
-        let player_entities = self.remote_to_local.get_mut(&remote.player()).unwrap();
+    fn deregister(&mut self, remote: EntityNet) -> Option<Entity> {
+        let player_entities = self.remote_to_local.get_mut(&remote.player())?;
         let local = player_entities.remove(remote.index()).unwrap();
         self.local_to_remote.remove(&local).unwrap();
-        local
+        Some(local)
     }
 
     /// Translates local entity ID to a remote entity ID in case the entity is
@@ -374,8 +376,9 @@ fn recv_messages(
                 ));
             }
             ToPlayers::Despawn { entity } => {
-                let local = net_commands.deregister(*entity);
-                despawn_events.send(NetRecvDespawnActiveEvent::new(local));
+                if let Some(local) = net_commands.deregister(*entity) {
+                    despawn_events.send(NetRecvDespawnActiveEvent::new(local));
+                }
             }
             ToPlayers::SetPath { entity, waypoints } => {
                 let Some(local) = net_commands.remote_local_id(*entity) else {

From 7a27fefcaeee2420e01bdb8ed1b32c6016cd9298 Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Thu, 12 Oct 2023 18:01:16 +0200
Subject: [PATCH 09/18] de_messages: Add nalgebra conversion impls (#769)

---
 Cargo.lock                          |  1 +
 crates/messages/Cargo.toml          |  1 +
 crates/messages/src/players/geom.rs | 97 +++++++++++++++++++++++++++++
 3 files changed, 99 insertions(+)

diff --git a/Cargo.lock b/Cargo.lock
index c1d52869..6cbd0755 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2523,6 +2523,7 @@ dependencies = [
  "bincode",
  "de_types",
  "glam",
+ "nalgebra",
  "thiserror",
 ]
 
diff --git a/crates/messages/Cargo.toml b/crates/messages/Cargo.toml
index f89a86fb..140a7662 100644
--- a/crates/messages/Cargo.toml
+++ b/crates/messages/Cargo.toml
@@ -22,4 +22,5 @@ de_types.workspace = true
 bevy = { workspace = true, optional = true }
 bincode.workspace = true
 glam.workspace = true
+nalgebra.workspace = true
 thiserror.workspace = true
diff --git a/crates/messages/src/players/geom.rs b/crates/messages/src/players/geom.rs
index 34f62b06..a9440698 100644
--- a/crates/messages/src/players/geom.rs
+++ b/crates/messages/src/players/geom.rs
@@ -4,6 +4,7 @@ use bincode::{Decode, Encode};
 #[cfg(feature = "bevy")]
 use glam::Quat;
 use glam::{Vec2, Vec3, Vec4};
+use nalgebra::{Point2, Point3, Point4, Vector2, Vector3, Vector4};
 
 /// Network representation of translation and rotation. Note that scale is
 /// assumed to be always 1.0 along all axes.
@@ -58,12 +59,42 @@ impl From<Vec2> for Vec2Net {
     }
 }
 
+impl From<Point2<f32>> for Vec2Net {
+    fn from(point: Point2<f32>) -> Self {
+        Self {
+            x: point.x,
+            y: point.y,
+        }
+    }
+}
+
+impl From<Vector2<f32>> for Vec2Net {
+    fn from(vector: Vector2<f32>) -> Self {
+        Self {
+            x: vector.x,
+            y: vector.y,
+        }
+    }
+}
+
 impl From<Vec2Net> for Vec2 {
     fn from(vec: Vec2Net) -> Self {
         Self::new(vec.x, vec.y)
     }
 }
 
+impl From<Vec2Net> for Point2<f32> {
+    fn from(vec: Vec2Net) -> Self {
+        Self::new(vec.x, vec.y)
+    }
+}
+
+impl From<Vec2Net> for Vector2<f32> {
+    fn from(vec: Vec2Net) -> Self {
+        Self::new(vec.x, vec.y)
+    }
+}
+
 #[derive(Clone, Copy, Debug, Encode, Decode)]
 pub struct Vec3Net {
     x: f32,
@@ -81,12 +112,44 @@ impl From<Vec3> for Vec3Net {
     }
 }
 
+impl From<Point3<f32>> for Vec3Net {
+    fn from(point: Point3<f32>) -> Self {
+        Self {
+            x: point.x,
+            y: point.y,
+            z: point.z,
+        }
+    }
+}
+
+impl From<Vector3<f32>> for Vec3Net {
+    fn from(vector: Vector3<f32>) -> Self {
+        Self {
+            x: vector.x,
+            y: vector.y,
+            z: vector.z,
+        }
+    }
+}
+
 impl From<Vec3Net> for Vec3 {
     fn from(vec: Vec3Net) -> Self {
         Self::new(vec.x, vec.y, vec.z)
     }
 }
 
+impl From<Vec3Net> for Point3<f32> {
+    fn from(vec: Vec3Net) -> Self {
+        Self::new(vec.x, vec.y, vec.z)
+    }
+}
+
+impl From<Vec3Net> for Vector3<f32> {
+    fn from(vec: Vec3Net) -> Self {
+        Self::new(vec.x, vec.y, vec.z)
+    }
+}
+
 #[derive(Clone, Copy, Debug, Encode, Decode)]
 pub struct Vec4Net {
     x: f32,
@@ -106,8 +169,42 @@ impl From<Vec4> for Vec4Net {
     }
 }
 
+impl From<Point4<f32>> for Vec4Net {
+    fn from(point: Point4<f32>) -> Self {
+        Self {
+            x: point.x,
+            y: point.y,
+            z: point.z,
+            w: point.w,
+        }
+    }
+}
+
+impl From<Vector4<f32>> for Vec4Net {
+    fn from(vector: Vector4<f32>) -> Self {
+        Self {
+            x: vector.x,
+            y: vector.y,
+            z: vector.z,
+            w: vector.w,
+        }
+    }
+}
+
 impl From<Vec4Net> for Vec4 {
     fn from(vec: Vec4Net) -> Self {
         Self::new(vec.x, vec.y, vec.z, vec.w)
     }
 }
+
+impl From<Vec4Net> for Point4<f32> {
+    fn from(vec: Vec4Net) -> Self {
+        Self::new(vec.x, vec.y, vec.z, vec.w)
+    }
+}
+
+impl From<Vec4Net> for Vector4<f32> {
+    fn from(vec: Vec4Net) -> Self {
+        Self::new(vec.x, vec.y, vec.z, vec.w)
+    }
+}

From c5a5d96710bdc21dcd669d4dd5509217b5751f2f Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Thu, 12 Oct 2023 18:14:44 +0200
Subject: [PATCH 10/18] Mutiplayer: sync laser trails (#768)

Closes #748.
---
 crates/combat/src/laser.rs                |  6 +-
 crates/combat/src/trail.rs                | 73 ++++++++++++++++++-----
 crates/messages/src/lib.rs                |  2 +-
 crates/messages/src/players/mod.rs        |  4 ++
 crates/messages/src/players/projectile.rs | 12 ++++
 crates/multiplayer/src/lib.rs             |  3 +-
 crates/multiplayer/src/messages.rs        |  1 +
 crates/multiplayer/src/playermsg.rs       | 10 +++-
 8 files changed, 91 insertions(+), 20 deletions(-)
 create mode 100644 crates/messages/src/players/projectile.rs

diff --git a/crates/combat/src/laser.rs b/crates/combat/src/laser.rs
index 8dda1648..e4a5daf3 100644
--- a/crates/combat/src/laser.rs
+++ b/crates/combat/src/laser.rs
@@ -6,7 +6,7 @@ use parry3d::query::Ray;
 use crate::{
     health::{HealthSet, LocalUpdateHealthEvent},
     sightline::LineOfSight,
-    trail::TrailEvent,
+    trail::LocalLaserTrailEvent,
     AttackingSet,
 };
 
@@ -80,13 +80,13 @@ fn fire(
     mut fires: EventReader<LaserFireEvent>,
     sightline: LineOfSight,
     mut health: EventWriter<LocalUpdateHealthEvent>,
-    mut trail: EventWriter<TrailEvent>,
+    mut trail: EventWriter<LocalLaserTrailEvent>,
     mut start_sound: EventWriter<PlaySpatialAudioEvent>,
 ) {
     for fire in fires.iter() {
         let observation = sightline.sight(fire.ray(), fire.max_toi(), fire.attacker());
 
-        trail.send(TrailEvent::new(Ray::new(
+        trail.send(LocalLaserTrailEvent::new(Ray::new(
             fire.ray().origin,
             observation.toi() * fire.ray().dir,
         )));
diff --git a/crates/combat/src/trail.rs b/crates/combat/src/trail.rs
index cf311ce3..a1849e56 100644
--- a/crates/combat/src/trail.rs
+++ b/crates/combat/src/trail.rs
@@ -11,7 +11,11 @@ use bevy::{
         },
     },
 };
-use de_core::{cleanup::DespawnOnGameExit, gamestate::GameState, state::AppState};
+use de_core::{
+    cleanup::DespawnOnGameExit, gamestate::GameState, gconfig::GameConfig, state::AppState,
+};
+use de_messages::{NetProjectile, ToPlayers};
+use de_multiplayer::{MessagesSet, NetRecvProjectileEvent, ToPlayersEvent};
 use parry3d::query::Ray;
 
 const TRAIL_LIFESPAN: Duration = Duration::from_millis(500);
@@ -22,20 +26,29 @@ pub(crate) struct TrailPlugin;
 impl Plugin for TrailPlugin {
     fn build(&self, app: &mut App) {
         app.add_plugins(MaterialPlugin::<TrailMaterial>::default())
-            .add_event::<TrailEvent>()
+            .add_event::<LocalLaserTrailEvent>()
+            .add_event::<LaserTrailEvent>()
             .add_systems(OnEnter(AppState::InGame), setup)
             .add_systems(OnExit(AppState::InGame), cleanup)
             .add_systems(
                 PostUpdate,
-                (spawn, update).run_if(in_state(GameState::Playing)),
+                (
+                    local_laser_trail
+                        .before(MessagesSet::SendMessages)
+                        .before(laser_trail),
+                    remote_laser_trail.before(laser_trail),
+                    laser_trail,
+                    update,
+                )
+                    .run_if(in_state(GameState::Playing)),
             );
     }
 }
 
 #[derive(Event)]
-pub(crate) struct TrailEvent(Ray);
+pub(crate) struct LocalLaserTrailEvent(Ray);
 
-impl TrailEvent {
+impl LocalLaserTrailEvent {
     /// Send this event to spawn a new trail. The trail will automatically fade
     /// out and disappear.
     ///
@@ -46,12 +59,11 @@ impl TrailEvent {
     pub(crate) fn new(ray: Ray) -> Self {
         Self(ray)
     }
-
-    fn ray(&self) -> &Ray {
-        &self.0
-    }
 }
 
+#[derive(Event)]
+struct LaserTrailEvent(Ray);
+
 #[derive(Resource)]
 struct MeshHandle(Handle<Mesh>);
 
@@ -104,12 +116,45 @@ impl Material for TrailMaterial {
     }
 }
 
-fn spawn(
+fn local_laser_trail(
+    config: Res<GameConfig>,
+    mut in_events: EventReader<LocalLaserTrailEvent>,
+    mut out_events: EventWriter<LaserTrailEvent>,
+    mut net_events: EventWriter<ToPlayersEvent>,
+) {
+    for event in in_events.iter() {
+        out_events.send(LaserTrailEvent(event.0));
+
+        if config.multiplayer() {
+            net_events.send(ToPlayersEvent::new(ToPlayers::Projectile(
+                NetProjectile::Laser {
+                    origin: event.0.origin.into(),
+                    direction: event.0.dir.into(),
+                },
+            )));
+        }
+    }
+}
+
+fn remote_laser_trail(
+    mut in_events: EventReader<NetRecvProjectileEvent>,
+    mut out_events: EventWriter<LaserTrailEvent>,
+) {
+    for event in in_events.iter() {
+        match **event {
+            NetProjectile::Laser { origin, direction } => {
+                out_events.send(LaserTrailEvent(Ray::new(origin.into(), direction.into())));
+            }
+        }
+    }
+}
+
+fn laser_trail(
     mut commands: Commands,
     mut materials: ResMut<Assets<TrailMaterial>>,
     time: Res<Time>,
     mesh: Res<MeshHandle>,
-    mut events: EventReader<TrailEvent>,
+    mut events: EventReader<LaserTrailEvent>,
 ) {
     for event in events.iter() {
         let material = materials.add(TrailMaterial::new(time.elapsed_seconds_wrapped()));
@@ -119,9 +164,9 @@ fn spawn(
                 mesh: mesh.0.clone(),
                 material,
                 transform: Transform {
-                    translation: event.ray().origin.into(),
-                    rotation: Quat::from_rotation_arc(Vec3::X, event.ray().dir.normalize().into()),
-                    scale: Vec3::new(event.ray().dir.norm(), 1., 1.),
+                    translation: event.0.origin.into(),
+                    rotation: Quat::from_rotation_arc(Vec3::X, event.0.dir.normalize().into()),
+                    scale: Vec3::new(event.0.dir.norm(), 1., 1.),
                 },
                 ..Default::default()
             },
diff --git a/crates/messages/src/lib.rs b/crates/messages/src/lib.rs
index b8440fe9..93e3f9a4 100644
--- a/crates/messages/src/lib.rs
+++ b/crates/messages/src/lib.rs
@@ -4,7 +4,7 @@
 pub use game::{FromGame, JoinError, Readiness, ToGame};
 pub use players::{
     BorrowedFromPlayers, ChatMessage, ChatMessageError, EntityNet, FromPlayers, HealthDelta,
-    NetEntityIndex, ToPlayers, MAX_CHAT_LEN,
+    NetEntityIndex, NetProjectile, ToPlayers, MAX_CHAT_LEN,
 };
 pub use server::{FromServer, GameOpenError, ToServer};
 
diff --git a/crates/messages/src/players/mod.rs b/crates/messages/src/players/mod.rs
index ad452e13..57f12ac5 100644
--- a/crates/messages/src/players/mod.rs
+++ b/crates/messages/src/players/mod.rs
@@ -4,11 +4,13 @@ use de_types::{objects::ActiveObjectType, player::Player};
 pub use entity::{EntityNet, NetEntityIndex};
 pub use geom::{TransformNet, Vec2Net, Vec3Net, Vec4Net};
 pub use path::{PathError, PathNet};
+pub use projectile::NetProjectile;
 
 mod chat;
 mod entity;
 mod geom;
 mod path;
+mod projectile;
 
 /// Messages to be sent by a player/client or occasionally the game server to
 /// other players.
@@ -85,6 +87,8 @@ pub enum ToPlayers {
         entity: EntityNet,
         delta: HealthDelta,
     },
+    /// Some kind of projectile was spawned (e.g. rocket, laser trail).
+    Projectile(NetProjectile),
 }
 
 #[derive(Debug, Encode, Decode)]
diff --git a/crates/messages/src/players/projectile.rs b/crates/messages/src/players/projectile.rs
new file mode 100644
index 00000000..63ef446f
--- /dev/null
+++ b/crates/messages/src/players/projectile.rs
@@ -0,0 +1,12 @@
+use bincode::{Decode, Encode};
+
+use crate::players::Vec3Net;
+
+#[derive(Clone, Copy, Debug, Encode, Decode)]
+pub enum NetProjectile {
+    Laser {
+        origin: Vec3Net,
+        /// End of the trail lies at `origin + direction`.
+        direction: Vec3Net,
+    },
+}
diff --git a/crates/multiplayer/src/lib.rs b/crates/multiplayer/src/lib.rs
index 3ca6eb6c..e686a1c5 100644
--- a/crates/multiplayer/src/lib.rs
+++ b/crates/multiplayer/src/lib.rs
@@ -25,7 +25,8 @@ pub use crate::{
     netstate::NetState,
     playermsg::{
         GameNetSet, NetEntities, NetEntityCommands, NetRecvDespawnActiveEvent, NetRecvHealthEvent,
-        NetRecvSetPathEvent, NetRecvSpawnActiveEvent, NetRecvTransformEvent,
+        NetRecvProjectileEvent, NetRecvSetPathEvent, NetRecvSpawnActiveEvent,
+        NetRecvTransformEvent,
     },
 };
 use crate::{netstate::NetStatePlugin, network::NetworkPlugin};
diff --git a/crates/multiplayer/src/messages.rs b/crates/multiplayer/src/messages.rs
index 8b7d7ff8..481e58e6 100644
--- a/crates/multiplayer/src/messages.rs
+++ b/crates/multiplayer/src/messages.rs
@@ -144,6 +144,7 @@ impl ToMessage for ToPlayersEvent {
             ToPlayers::SetPath { .. } => Reliability::SemiOrdered,
             ToPlayers::Transform { .. } => Reliability::Unreliable,
             ToPlayers::ChangeHealth { .. } => Reliability::SemiOrdered,
+            ToPlayers::Projectile(_) => Reliability::Unreliable,
         }
     }
 
diff --git a/crates/multiplayer/src/playermsg.rs b/crates/multiplayer/src/playermsg.rs
index 3a839682..7a058baf 100644
--- a/crates/multiplayer/src/playermsg.rs
+++ b/crates/multiplayer/src/playermsg.rs
@@ -4,7 +4,7 @@ use bevy::{
     prelude::*,
 };
 use de_core::{gconfig::GameConfig, schedule::PreMovement, state::AppState};
-use de_messages::{EntityNet, NetEntityIndex, ToPlayers};
+use de_messages::{EntityNet, NetEntityIndex, NetProjectile, ToPlayers};
 use de_types::{objects::ActiveObjectType, path::Path, player::Player};
 
 use crate::messages::{FromPlayersEvent, MessagesSet};
@@ -19,6 +19,7 @@ impl Plugin for PlayerMsgPlugin {
             .add_event::<NetRecvHealthEvent>()
             .add_event::<NetRecvTransformEvent>()
             .add_event::<NetRecvSetPathEvent>()
+            .add_event::<NetRecvProjectileEvent>()
             .add_systems(OnEnter(AppState::InGame), setup)
             .add_systems(OnExit(AppState::InGame), cleanup)
             .add_systems(
@@ -166,6 +167,9 @@ impl NetRecvSetPathEvent {
     }
 }
 
+#[derive(Event, Deref)]
+pub struct NetRecvProjectileEvent(NetProjectile);
+
 #[derive(SystemParam)]
 pub struct NetEntities<'w> {
     config: Res<'w, GameConfig>,
@@ -356,6 +360,7 @@ fn recv_messages(
     mut path_events: EventWriter<NetRecvSetPathEvent>,
     mut transform_events: EventWriter<NetRecvTransformEvent>,
     mut health_events: EventWriter<NetRecvHealthEvent>,
+    mut projectile_events: EventWriter<NetRecvProjectileEvent>,
 ) {
     for input in inputs.iter() {
         match input.message() {
@@ -404,6 +409,9 @@ fn recv_messages(
 
                 health_events.send(NetRecvHealthEvent::new(local, delta.into()));
             }
+            ToPlayers::Projectile(projectile) => {
+                projectile_events.send(NetRecvProjectileEvent(*projectile));
+            }
             _ => (),
         }
     }

From 4b89fc34b8f73df4eb1d3a352b70e7c9119454a6 Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Thu, 12 Oct 2023 18:22:38 +0200
Subject: [PATCH 11/18] Play laser fire sound even for remote players (#771)

Fixes #770.
---
 crates/combat/src/laser.rs |  7 -------
 crates/combat/src/trail.rs | 25 ++++++++++++++++++++++---
 2 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/crates/combat/src/laser.rs b/crates/combat/src/laser.rs
index e4a5daf3..7fb86bce 100644
--- a/crates/combat/src/laser.rs
+++ b/crates/combat/src/laser.rs
@@ -1,5 +1,4 @@
 use bevy::prelude::*;
-use de_audio::spatial::{PlaySpatialAudioEvent, Sound};
 use de_core::gamestate::GameState;
 use parry3d::query::Ray;
 
@@ -81,7 +80,6 @@ fn fire(
     sightline: LineOfSight,
     mut health: EventWriter<LocalUpdateHealthEvent>,
     mut trail: EventWriter<LocalLaserTrailEvent>,
-    mut start_sound: EventWriter<PlaySpatialAudioEvent>,
 ) {
     for fire in fires.iter() {
         let observation = sightline.sight(fire.ray(), fire.max_toi(), fire.attacker());
@@ -94,10 +92,5 @@ fn fire(
         if let Some(entity) = observation.entity() {
             health.send(LocalUpdateHealthEvent::new(entity, -fire.damage()));
         }
-
-        start_sound.send(PlaySpatialAudioEvent::new(
-            Sound::LaserFire,
-            fire.ray().origin.into(),
-        ));
     }
 }
diff --git a/crates/combat/src/trail.rs b/crates/combat/src/trail.rs
index a1849e56..5d3af1f8 100644
--- a/crates/combat/src/trail.rs
+++ b/crates/combat/src/trail.rs
@@ -11,6 +11,7 @@ use bevy::{
         },
     },
 };
+use de_audio::spatial::{PlaySpatialAudioEvent, Sound};
 use de_core::{
     cleanup::DespawnOnGameExit, gamestate::GameState, gconfig::GameConfig, state::AppState,
 };
@@ -35,9 +36,10 @@ impl Plugin for TrailPlugin {
                 (
                     local_laser_trail
                         .before(MessagesSet::SendMessages)
-                        .before(laser_trail),
-                    remote_laser_trail.before(laser_trail),
-                    laser_trail,
+                        .before(TrailSet::Trail),
+                    remote_laser_trail.before(TrailSet::Trail),
+                    laser_trail.in_set(TrailSet::Trail),
+                    laser_sound.in_set(TrailSet::Trail),
                     update,
                 )
                     .run_if(in_state(GameState::Playing)),
@@ -45,6 +47,11 @@ impl Plugin for TrailPlugin {
     }
 }
 
+#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemSet)]
+enum TrailSet {
+    Trail,
+}
+
 #[derive(Event)]
 pub(crate) struct LocalLaserTrailEvent(Ray);
 
@@ -178,6 +185,18 @@ fn laser_trail(
     }
 }
 
+fn laser_sound(
+    mut events: EventReader<LaserTrailEvent>,
+    mut sound_events: EventWriter<PlaySpatialAudioEvent>,
+) {
+    for event in events.iter() {
+        sound_events.send(PlaySpatialAudioEvent::new(
+            Sound::LaserFire,
+            event.0.origin.into(),
+        ));
+    }
+}
+
 fn update(mut commands: Commands, time: Res<Time>, mut query: Query<(Entity, &mut Trail)>) {
     for (entity, mut trail) in query.iter_mut() {
         trail.tick(time.delta());

From 1658c43c59f6ec9effd8e26beb896cbee8660bd8 Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Tue, 17 Oct 2023 15:47:47 +0200
Subject: [PATCH 12/18] de_index: Prep for faster indexing alternative (#772)

Currently, only collider based spatial indexing is implemented. It
allows reasonably fast spatial queries, for example ray casting or AABB
search applied on each entity collider.

It is the plan to extend `de_index` with point based indexing (as
opposed to collider based indexing). Point based spatial queries have to
potential to be much faster and yet sufficient for some important
use-cases (i.e. energy grid graph construction). This PR reorganizes
`de_index` so that we can add the alternative indexing alongside it.

Relates to #472.
---
 crates/index/src/lib.rs                       | 30 ++++++-----------
 crates/index/src/{ => precise}/aabb.rs        |  6 ++--
 crates/index/src/{ => precise}/collider.rs    |  8 ++---
 crates/index/src/{ => precise}/grid.rs        | 14 ++++----
 crates/index/src/{ => precise}/index.rs       | 16 ++++++----
 .../index/src/{systems.rs => precise/mod.rs}  | 32 +++++++++++++------
 crates/index/src/{ => precise}/range.rs       | 10 +++---
 crates/index/src/{ => precise}/segment.rs     |  8 ++---
 crates/spawner/src/draft.rs                   |  4 +--
 9 files changed, 65 insertions(+), 63 deletions(-)
 rename crates/index/src/{ => precise}/aabb.rs (95%)
 rename crates/index/src/{ => precise}/collider.rs (92%)
 rename crates/index/src/{ => precise}/grid.rs (96%)
 rename crates/index/src/{ => precise}/index.rs (96%)
 rename crates/index/src/{systems.rs => precise/mod.rs} (79%)
 rename crates/index/src/{ => precise}/range.rs (88%)
 rename crates/index/src/{ => precise}/segment.rs (97%)

diff --git a/crates/index/src/lib.rs b/crates/index/src/lib.rs
index b82506c0..e28ffd1b 100644
--- a/crates/index/src/lib.rs
+++ b/crates/index/src/lib.rs
@@ -1,26 +1,14 @@
 #![allow(rustdoc::private_intra_doc_links)]
-//! This module implements 2D object partitioning for fast geometric lookup,
-//! for example ray casting.
-//!
-//! The core structure is a square tile grid which points to Bevy ECS entities.
-//! Newly spawned entities are automatically added, despawned entities removed
-//! and moved entities updated by systems added by
-//! [`self::IndexPlugin`].
-mod aabb;
-mod collider;
-mod grid;
-mod index;
-mod range;
-mod segment;
-mod systems;
+//! This crate implements spatial indexing and various spatial queries of game
+//! entities.
 
-use bevy::{app::PluginGroupBuilder, prelude::PluginGroup};
-use systems::IndexPlugin;
+mod precise;
 
-pub use self::{
-    collider::{ColliderWithCache, LocalCollider, QueryCollider},
-    index::{EntityIndex, RayEntityIntersection, SpatialQuery},
-    systems::IndexSet,
+use bevy::{app::PluginGroupBuilder, prelude::PluginGroup};
+use precise::PreciseIndexPlugin;
+pub use precise::{
+    ColliderWithCache, EntityIndex, LocalCollider, PreciseIndexSet, QueryCollider,
+    RayEntityIntersection, SpatialQuery,
 };
 
 /// Size (in world-space) of a single square tile where entities are kept.
@@ -30,6 +18,6 @@ pub struct IndexPluginGroup;
 
 impl PluginGroup for IndexPluginGroup {
     fn build(self) -> PluginGroupBuilder {
-        PluginGroupBuilder::start::<Self>().add(IndexPlugin)
+        PluginGroupBuilder::start::<Self>().add(PreciseIndexPlugin)
     }
 }
diff --git a/crates/index/src/aabb.rs b/crates/index/src/precise/aabb.rs
similarity index 95%
rename from crates/index/src/aabb.rs
rename to crates/index/src/precise/aabb.rs
index b489b8d6..e7ed1691 100644
--- a/crates/index/src/aabb.rs
+++ b/crates/index/src/precise/aabb.rs
@@ -2,10 +2,10 @@ use ahash::AHashSet;
 use bevy::prelude::Entity;
 use parry3d::bounding_volume::Aabb;
 
-use crate::{grid::TileGrid, range::TileRange};
+use super::{grid::TileGrid, range::TileRange};
 
 /// An iterator over unique entity IDs withing a box.
-pub(crate) struct AabbCandidates<'a> {
+pub(super) struct AabbCandidates<'a> {
     grid: &'a TileGrid,
     tiles: TileRange,
     row: Option<i32>,
@@ -16,7 +16,7 @@ pub(crate) struct AabbCandidates<'a> {
 impl<'a> AabbCandidates<'a> {
     /// Creates a new iterator of entities potentially colliding with a given
     /// AABB.
-    pub(crate) fn new(grid: &'a TileGrid, aabb: &Aabb) -> Self {
+    pub(super) fn new(grid: &'a TileGrid, aabb: &Aabb) -> Self {
         Self {
             grid,
             tiles: TileRange::from_aabb(aabb),
diff --git a/crates/index/src/collider.rs b/crates/index/src/precise/collider.rs
similarity index 92%
rename from crates/index/src/collider.rs
rename to crates/index/src/precise/collider.rs
index 0af15d6c..c258a537 100644
--- a/crates/index/src/collider.rs
+++ b/crates/index/src/precise/collider.rs
@@ -43,12 +43,12 @@ impl LocalCollider {
     }
 
     /// Updates position of cached world-space AABB of the collider.
-    pub(crate) fn update_position(&mut self, position: Isometry<f32>) {
+    pub(super) fn update_position(&mut self, position: Isometry<f32>) {
         self.world_aabb = self.local_aabb.transform_by(&position);
         self.position = position;
     }
 
-    pub(crate) fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Option<f32> {
+    pub(super) fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Option<f32> {
         if self.world_aabb.intersects_local_ray(ray, max_toi) {
             self.object_collider.cast_ray(&self.position, ray, max_toi)
         } else {
@@ -56,7 +56,7 @@ impl LocalCollider {
         }
     }
 
-    pub(crate) fn intersects(&self, rhs: &impl ColliderWithCache) -> bool {
+    pub(super) fn intersects(&self, rhs: &impl ColliderWithCache) -> bool {
         if self.query_aabb(rhs.world_aabb()) {
             self.object_collider
                 .intersects(&self.position, rhs.inner(), rhs.position())
@@ -67,7 +67,7 @@ impl LocalCollider {
 
     /// Returns true if world-space axis-aligned bounding boxes of the two
     /// colliders intersect.
-    pub(crate) fn query_aabb(&self, aabb: &Aabb) -> bool {
+    pub(super) fn query_aabb(&self, aabb: &Aabb) -> bool {
         self.world_aabb.intersects(aabb)
     }
 }
diff --git a/crates/index/src/grid.rs b/crates/index/src/precise/grid.rs
similarity index 96%
rename from crates/index/src/grid.rs
rename to crates/index/src/precise/grid.rs
index 3025ee2d..5fb94693 100644
--- a/crates/index/src/grid.rs
+++ b/crates/index/src/precise/grid.rs
@@ -6,20 +6,20 @@ use bevy::prelude::Entity;
 use glam::IVec2;
 use parry3d::bounding_volume::Aabb;
 
-use crate::range::TileRange;
+use super::range::TileRange;
 
 /// Rectangular (2D) grid of sets of Bevy ECS entities.
 ///
 /// Only non-empty sets are kept (a hash map mapping 2D tile coordinates to
 /// Entity sets is used under the hood). Each set contains entities whose
 /// absolute AABB intersects with the tile.
-pub(crate) struct TileGrid {
+pub(super) struct TileGrid {
     tiles: AHashMap<IVec2, AHashSet<Entity>>,
 }
 
 impl TileGrid {
     /// Creates a new empty grid.
-    pub(crate) fn new() -> Self {
+    pub(super) fn new() -> Self {
         Self {
             tiles: AHashMap::new(),
         }
@@ -36,7 +36,7 @@ impl TileGrid {
     /// # Panics
     ///
     /// Might panic if the entity is already present in the grid.
-    pub(crate) fn insert(&mut self, entity: Entity, aabb: &Aabb) {
+    pub(super) fn insert(&mut self, entity: Entity, aabb: &Aabb) {
         for tile in TileRange::from_aabb(aabb) {
             self.insert_to_tile(entity, tile);
         }
@@ -56,7 +56,7 @@ impl TileGrid {
     ///
     /// Might panic if the entity is not stored in the grid or if the last used
     /// update / insertion AABB differs from the one passed as an argument.
-    pub(crate) fn remove(&mut self, entity: Entity, aabb: &Aabb) {
+    pub(super) fn remove(&mut self, entity: Entity, aabb: &Aabb) {
         for tile in TileRange::from_aabb(aabb) {
             self.remove_from_tile(entity, tile);
         }
@@ -77,7 +77,7 @@ impl TileGrid {
     ///
     /// Might panic if the entity is not present in the grid or if `old_aabb`
     /// differs from the last used update / insert AABB.
-    pub(crate) fn update(&mut self, entity: Entity, old_aabb: &Aabb, new_aabb: &Aabb) {
+    pub(super) fn update(&mut self, entity: Entity, old_aabb: &Aabb, new_aabb: &Aabb) {
         let old_tiles = TileRange::from_aabb(old_aabb);
         let new_tiles = TileRange::from_aabb(new_aabb);
 
@@ -107,7 +107,7 @@ impl TileGrid {
     /// # Arguments
     ///
     /// `tile_coords` - coordinates of the tile.
-    pub(crate) fn get_tile_entities(&self, tile_coords: IVec2) -> Option<&AHashSet<Entity>> {
+    pub(super) fn get_tile_entities(&self, tile_coords: IVec2) -> Option<&AHashSet<Entity>> {
         self.tiles.get(&tile_coords)
     }
 
diff --git a/crates/index/src/index.rs b/crates/index/src/precise/index.rs
similarity index 96%
rename from crates/index/src/index.rs
rename to crates/index/src/precise/index.rs
index 693f13a4..8cec2f81 100644
--- a/crates/index/src/index.rs
+++ b/crates/index/src/precise/index.rs
@@ -18,8 +18,10 @@ use parry3d::{
     shape::Segment,
 };
 
-use super::{collider::LocalCollider, grid::TileGrid, segment::SegmentCandidates};
-use crate::{aabb::AabbCandidates, collider::ColliderWithCache};
+use super::{
+    aabb::AabbCandidates, collider::ColliderWithCache, collider::LocalCollider, grid::TileGrid,
+    segment::SegmentCandidates,
+};
 
 /// 2D rectangular grid based spatial index of entities.
 #[derive(Resource)]
@@ -47,7 +49,7 @@ impl EntityIndex {
         self.colliders.insert(entity, collider);
     }
 
-    pub(crate) fn remove(&mut self, entity: Entity) {
+    pub(super) fn remove(&mut self, entity: Entity) {
         let collider = self
             .colliders
             .remove(&entity)
@@ -55,7 +57,7 @@ impl EntityIndex {
         self.grid.remove(entity, collider.world_aabb());
     }
 
-    pub(crate) fn update(&mut self, entity: Entity, position: Isometry<f32>) {
+    pub(super) fn update(&mut self, entity: Entity, position: Isometry<f32>) {
         let collider = self
             .colliders
             .get_mut(&entity)
@@ -107,7 +109,7 @@ impl Default for EntityIndex {
 /// System parameter implementing various spatial queries.
 ///
 /// Only entities automatically indexed by systems from
-/// [`super::systems::IndexPlugin`] could be retrieved.
+/// [`super::PreciseIndexPlugin`] could be retrieved.
 #[derive(SystemParam)]
 pub struct SpatialQuery<'w, 's, Q, F = ()>
 where
@@ -124,7 +126,7 @@ where
     F: ReadOnlyWorldQuery + Sync + Send + 'static,
 {
     /// Returns closest entity whose shape, as indexed by systems registered by
-    /// [`super::systems::IndexPlugin`], intersects a given ray.
+    /// [`super::PreciseIndexPlugin`], intersects a given ray.
     ///
     /// # Arguments
     ///
@@ -173,7 +175,7 @@ where
     }
 
     /// Returns true if queried solid object on the map, as indexed by
-    /// [`super::systems::IndexPlugin`], intersects with the given collider.
+    /// [`super::PreciseIndexPlugin`], intersects with the given collider.
     pub fn collides(&self, collider: &impl ColliderWithCache) -> bool {
         let candidate_sets = self.index.query_aabb(collider.world_aabb());
         candidate_sets.flatten().any(|candidate| {
diff --git a/crates/index/src/systems.rs b/crates/index/src/precise/mod.rs
similarity index 79%
rename from crates/index/src/systems.rs
rename to crates/index/src/precise/mod.rs
index 66f2ab70..f00bbe94 100644
--- a/crates/index/src/systems.rs
+++ b/crates/index/src/precise/mod.rs
@@ -1,6 +1,9 @@
-//! Module with systems and a Bevy plugin for automatic entity indexing of
-//! solid entities.
-
+//! This module implements collider based spatial indexing of game entities and
+//! various geometry based lookup (for example ray casting).
+//!
+//! The core structure is a square tile grid which points to Bevy ECS entities.
+//! Newly spawned entities are automatically added, despawned entities removed
+//! and moved entities updated by systems added by [`PreciseIndexPlugin`].
 use bevy::prelude::*;
 use de_core::{
     gamestate::GameState,
@@ -11,8 +14,17 @@ use de_core::{
 use de_objects::SolidObjects;
 use parry3d::math::Isometry;
 
-use super::index::EntityIndex;
-use crate::collider::LocalCollider;
+pub use self::{
+    collider::{ColliderWithCache, LocalCollider, QueryCollider},
+    index::{EntityIndex, RayEntityIntersection, SpatialQuery},
+};
+
+mod aabb;
+mod collider;
+mod grid;
+mod index;
+mod range;
+mod segment;
 
 type SolidEntityQuery<'w, 's> = Query<
     'w,
@@ -38,9 +50,9 @@ type MovedQuery<'w, 's> =
 /// insert newly spawned solid entities to the index, update their position
 /// when [`bevy::prelude::Transform`] is changed and remove the entities from
 /// the index when they are de-spawned.
-pub(crate) struct IndexPlugin;
+pub(super) struct PreciseIndexPlugin;
 
-impl Plugin for IndexPlugin {
+impl Plugin for PreciseIndexPlugin {
     fn build(&self, app: &mut App) {
         app.add_systems(OnEnter(AppState::InGame), setup)
             .add_systems(OnExit(AppState::InGame), cleanup)
@@ -48,19 +60,19 @@ impl Plugin for IndexPlugin {
                 PostUpdate,
                 (insert, remove)
                     .run_if(in_state(GameState::Playing))
-                    .in_set(IndexSet::Index),
+                    .in_set(PreciseIndexSet::Index),
             )
             .add_systems(
                 PostMovement,
                 update
                     .run_if(in_state(GameState::Playing))
-                    .in_set(IndexSet::Index),
+                    .in_set(PreciseIndexSet::Index),
             );
     }
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)]
-pub enum IndexSet {
+pub enum PreciseIndexSet {
     Index,
 }
 
diff --git a/crates/index/src/range.rs b/crates/index/src/precise/range.rs
similarity index 88%
rename from crates/index/src/range.rs
rename to crates/index/src/precise/range.rs
index a3e206f4..7dfe2dea 100644
--- a/crates/index/src/range.rs
+++ b/crates/index/src/precise/range.rs
@@ -8,7 +8,7 @@ use crate::TILE_SIZE;
 ///
 /// The tiles are iterated row-by-row, for example: (1, 1) -> (2, 1) -> (1, 2)
 /// -> (2, 2).
-pub(crate) struct TileRange {
+pub(super) struct TileRange {
     a: IVec2,
     b: IVec2,
     x: i32,
@@ -21,7 +21,7 @@ impl TileRange {
     ///
     /// Tiles are assumed to be topologically closed. In other words, both
     /// touching and intersecting tiles are included in the range.
-    pub(crate) fn from_aabb(aabb: &Aabb) -> Self {
+    pub(super) fn from_aabb(aabb: &Aabb) -> Self {
         let aabb = aabb.to_flat();
         let min_flat: Vec2 = aabb.mins.into();
         let max_flat: Vec2 = aabb.maxs.into();
@@ -35,7 +35,7 @@ impl TileRange {
     /// * `a` - inclusive range start.
     ///
     /// * `b` - inclusive range end.
-    pub(crate) fn new(a: IVec2, b: IVec2) -> Self {
+    pub(super) fn new(a: IVec2, b: IVec2) -> Self {
         Self {
             a,
             b,
@@ -46,12 +46,12 @@ impl TileRange {
     }
 
     /// Returns true if the given point is not contained in the tile range.
-    pub(crate) fn excludes(&self, point: IVec2) -> bool {
+    pub(super) fn excludes(&self, point: IVec2) -> bool {
         self.a.cmpgt(point).any() || self.b.cmplt(point).any()
     }
 
     /// Returns intersecting tile range. The result might be empty.
-    pub(crate) fn intersection(&self, other: &TileRange) -> TileRange {
+    pub(super) fn intersection(&self, other: &TileRange) -> TileRange {
         Self::new(self.a.max(other.a), self.b.min(other.b))
     }
 }
diff --git a/crates/index/src/segment.rs b/crates/index/src/precise/segment.rs
similarity index 97%
rename from crates/index/src/segment.rs
rename to crates/index/src/precise/segment.rs
index b72825ad..f7d2e912 100644
--- a/crates/index/src/segment.rs
+++ b/crates/index/src/precise/segment.rs
@@ -6,7 +6,8 @@ use de_types::projection::ToFlat;
 use glam::{IVec2, Vec2};
 use parry3d::shape::Segment;
 
-use super::{grid::TileGrid, TILE_SIZE};
+use super::grid::TileGrid;
+use crate::TILE_SIZE;
 
 /// An iterator over sets of entities from tiles intersecting a given line
 /// segment.
@@ -18,14 +19,14 @@ use super::{grid::TileGrid, TILE_SIZE};
 /// The tiles (and thus the yielded sets) are iterated by increasing distance
 /// between point `a` of the given line segment and the intersection of the
 /// tile with the line segment.
-pub(crate) struct SegmentCandidates<'a> {
+pub(super) struct SegmentCandidates<'a> {
     grid: &'a TileGrid,
     tiles: TileIterator,
     encountered: Option<&'a AHashSet<Entity>>,
 }
 
 impl<'a> SegmentCandidates<'a> {
-    pub(crate) fn new(grid: &'a TileGrid, segment: Segment) -> Self {
+    pub(super) fn new(grid: &'a TileGrid, segment: Segment) -> Self {
         Self {
             grid,
             tiles: TileIterator::new(segment),
@@ -172,7 +173,6 @@ mod tests {
     use parry3d::{bounding_volume::Aabb, math::Point, shape::Segment};
 
     use super::*;
-    use crate::grid::TileGrid;
 
     #[test]
     fn test_segment_candidates() {
diff --git a/crates/spawner/src/draft.rs b/crates/spawner/src/draft.rs
index 6d57591b..5c9fb356 100644
--- a/crates/spawner/src/draft.rs
+++ b/crates/spawner/src/draft.rs
@@ -12,7 +12,7 @@ use de_core::{
     objects::{MovableSolid, ObjectTypeComponent, StaticSolid},
     state::AppState,
 };
-use de_index::{ColliderWithCache, IndexSet, QueryCollider, SpatialQuery};
+use de_index::{ColliderWithCache, PreciseIndexSet, QueryCollider, SpatialQuery};
 use de_map::size::MapBounds;
 use de_objects::{AssetCollection, SceneType, Scenes, SolidObjects, EXCLUSION_OFFSET};
 use de_types::{
@@ -42,7 +42,7 @@ impl Plugin for DraftPlugin {
                 PostUpdate,
                 (update_draft, check_draft_loaded, update_draft_colour)
                     .run_if(in_state(GameState::Playing))
-                    .after(IndexSet::Index),
+                    .after(PreciseIndexSet::Index),
             );
     }
 }

From 828b8fc76c85bbe0331cade2002a3160da74cacc Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 Oct 2023 09:36:39 +0200
Subject: [PATCH 13/18] Bump rustix from 0.37.24 to 0.37.25 (#774)

Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.37.24
to 0.37.25.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bytecodealliance/rustix/commit/00b84d6aac2364455eab2c68e42afee63d6e3ad3"><code>00b84d6</code></a>
chore: Release rustix version 0.37.25</li>
<li><a
href="https://github.com/bytecodealliance/rustix/commit/cad15a7076d493a0651fb0b7889bd5e5a72a8f17"><code>cad15a7</code></a>
Fixes for <code>Dir</code> on macOS, FreeBSD, and WASI.</li>
<li><a
href="https://github.com/bytecodealliance/rustix/commit/df3c3a192cf144af0da8a57417fb4addbdc611f6"><code>df3c3a1</code></a>
Merge pull request from GHSA-c827-hfw6-qwvm</li>
<li>See full diff in <a
href="https://github.com/bytecodealliance/rustix/compare/v0.37.24...v0.37.25">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rustix&package-manager=cargo&previous-version=0.37.24&new-version=0.37.25)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/DigitalExtinction/Game/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 6cbd0755..81d711d8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -576,7 +576,7 @@ dependencies = [
  "log",
  "parking",
  "polling",
- "rustix 0.37.24",
+ "rustix 0.37.25",
  "slab",
  "socket2 0.4.9",
  "waker-fn",
@@ -604,7 +604,7 @@ dependencies = [
  "cfg-if",
  "event-listener",
  "futures-lite",
- "rustix 0.37.24",
+ "rustix 0.37.25",
  "signal-hook",
  "windows-sys 0.48.0",
 ]
@@ -5294,9 +5294,9 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.37.24"
+version = "0.37.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d"
+checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035"
 dependencies = [
  "bitflags 1.3.2",
  "errno",

From 13cacecf88554e5999cada35664a8e8aaaed04b9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 4 Dec 2023 08:17:29 +0100
Subject: [PATCH 14/18] Bump openssl from 0.10.57 to 0.10.60 (#776)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.57
to 0.10.60.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sfackler/rust-openssl/releases">openssl's
releases</a>.</em></p>
<blockquote>
<h2>openssl-v0.10.60</h2>
<h2>What's Changed</h2>
<ul>
<li>Correct off-by-one in minimum output buffer size computation by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2088">sfackler/rust-openssl#2088</a></li>
<li>Expose a few more (bad) ciphers in cipher::Cipher by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2084">sfackler/rust-openssl#2084</a></li>
<li>add temp key bindings by <a
href="https://github.com/jmayclin"><code>@​jmayclin</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2076">sfackler/rust-openssl#2076</a></li>
<li>Expose ChaCha20 on LibreSSL by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2093">sfackler/rust-openssl#2093</a></li>
<li>Revert &quot;Correct off-by-one in minimum output buffer size
computation&quot; by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2090">sfackler/rust-openssl#2090</a></li>
<li>Added <code>update_unchecked</code> to <code>symm::Crypter</code> by
<a href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2100">sfackler/rust-openssl#2100</a></li>
<li>fixes <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2096">#2096</a>
-- deprecate <code>X509StoreRef::objects</code>, it is unsound by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2099">sfackler/rust-openssl#2099</a></li>
<li>Don't leak when overwriting ex data by <a
href="https://github.com/sfackler"><code>@​sfackler</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2102">sfackler/rust-openssl#2102</a></li>
<li>Release openssl v0.10.60 and openssl-sys v0.9.96 by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2104">sfackler/rust-openssl#2104</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.59...openssl-v0.10.60">https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.59...openssl-v0.10.60</a></p>
<h2>openssl-v0.10.59</h2>
<h2>What's Changed</h2>
<ul>
<li>Add binding to NID of Chacha20-Poly1305 cipher by <a
href="https://github.com/Arnavion"><code>@​Arnavion</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2081">sfackler/rust-openssl#2081</a></li>
<li>Fixed cfg for RSA_PSS by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2079">sfackler/rust-openssl#2079</a></li>
<li>fixes <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2050">#2050</a>
-- build and test on libressl 3.8.2 by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2082">sfackler/rust-openssl#2082</a></li>
<li>Release openssl v0.10.59 and openssl-sys v0.9.95 by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2083">sfackler/rust-openssl#2083</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Arnavion"><code>@​Arnavion</code></a>
made their first contribution in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2081">sfackler/rust-openssl#2081</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.58...openssl-v0.10.59">https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.58...openssl-v0.10.59</a></p>
<h2>openssl-v0.10.58</h2>
<h2>What's Changed</h2>
<ul>
<li>LibreSSL 3.8.1 support by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2035">sfackler/rust-openssl#2035</a></li>
<li>Update vendored version to openssl 3 by <a
href="https://github.com/amousset"><code>@​amousset</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/1925">sfackler/rust-openssl#1925</a></li>
<li>Test against 3.2.0-alpha1 by <a
href="https://github.com/sfackler"><code>@​sfackler</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2037">sfackler/rust-openssl#2037</a></li>
<li>Removed reference to non-existent method by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2039">sfackler/rust-openssl#2039</a></li>
<li>Bump CI to 1.1.1w by <a
href="https://github.com/sfackler"><code>@​sfackler</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2040">sfackler/rust-openssl#2040</a></li>
<li>[openssl-sys] Add X509_check_{host,email,ip,ip_asc} fns by <a
href="https://github.com/jgallagher"><code>@​jgallagher</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2042">sfackler/rust-openssl#2042</a></li>
<li>Expose CBC mode for several more (bad) ciphers by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2045">sfackler/rust-openssl#2045</a></li>
<li>Expose two additional Pkey IDs by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2046">sfackler/rust-openssl#2046</a></li>
<li>Add support for CRL extensions and the Authority Information Access
e… by <a
href="https://github.com/AdmiralGT"><code>@​AdmiralGT</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2003">sfackler/rust-openssl#2003</a></li>
<li>Fix clippy warnings produced by newer Rust by <a
href="https://github.com/wiktor-k"><code>@​wiktor-k</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2052">sfackler/rust-openssl#2052</a></li>
<li>Use osslconf on BoringSSL by <a
href="https://github.com/alex"><code>@​alex</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2056">sfackler/rust-openssl#2056</a></li>
<li>Make X509_ALGOR opaque for LibreSSL by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2060">sfackler/rust-openssl#2060</a></li>
<li>Don't ignore ECDSA tests without GF2m support by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2061">sfackler/rust-openssl#2061</a></li>
<li>Clarify 'possible LibreSSL bug' by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2062">sfackler/rust-openssl#2062</a></li>
<li>Enable BN_mod_sqrt() for upcoming LibreSSL 3.8.2 by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2063">sfackler/rust-openssl#2063</a></li>
<li>Enable SHA-3 for LibreSSL 3.8.0 by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2064">sfackler/rust-openssl#2064</a></li>
<li>Remove DH_generate_parameters for LibreSSL 3.8.2 by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2065">sfackler/rust-openssl#2065</a></li>
<li>Use EVP_MD_CTX_{new,free}() in LibreSSL 3.8.2 by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2067">sfackler/rust-openssl#2067</a></li>
<li>Enable HKDF support for LibreSSL &gt;= 3.6.0 by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2066">sfackler/rust-openssl#2066</a></li>
<li>Two build script fixes for LibreSSL by <a
href="https://github.com/botovq"><code>@​botovq</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2068">sfackler/rust-openssl#2068</a></li>
<li>Respect OPENSSL_NO_OCB on AES functions by <a
href="https://github.com/GuyLewin"><code>@​GuyLewin</code></a> in <a
href="https://redirect.github.com/sfackler/rust-openssl/pull/2070">sfackler/rust-openssl#2070</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/8f4b97ae06f5975e02052b5a4d4f63496a1ec760"><code>8f4b97a</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2104">#2104</a>
from alex/bump-for-release</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/df66283bbc734f20b968357bfc336def7b309b15"><code>df66283</code></a>
Release openssl v0.10.60 and openssl-sys v0.9.96</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/1a09dc8c948fcda66db0f221461303e80e69818e"><code>1a09dc8</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2102">#2102</a>
from sfackler/ex-leak</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/b0a1da5ee9f1b923af2d98a36a4805715826ce02"><code>b0a1da5</code></a>
Merge branch 'master' into ex-leak</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/f456b609365c9e04a5049415d6dff82cc885edd3"><code>f456b60</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2099">#2099</a>
from alex/deprecate-store-ref-objects</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/a8413b8b5414af3b9754f02ff9f3b0d91ca57cb1"><code>a8413b8</code></a>
Merge pull request <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2100">#2100</a>
from alex/symm-update-unchecked</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/a92c23794149dc6bec8a8b1148c68bbe048851c9"><code>a92c237</code></a>
clippy</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/e839496d9ed0bd4dcd4f1ec24e049cbe117ef1bb"><code>e839496</code></a>
Don't leak when overwriting ex data</li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/602d38dca7b8a22a355e1e53199d922742025c5c"><code>602d38d</code></a>
Added <code>update_unchecked</code> to <code>symm::Crypter</code></li>
<li><a
href="https://github.com/sfackler/rust-openssl/commit/cf9681a55cabd4cb9f1475bde17b5079f2a0384e"><code>cf9681a</code></a>
fixes <a
href="https://redirect.github.com/sfackler/rust-openssl/issues/2096">#2096</a>
-- deprecate <code>X509StoreRef::objects</code>, it is unsound</li>
<li>Additional commits viewable in <a
href="https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.57...openssl-v0.10.60">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=openssl&package-manager=cargo&previous-version=0.10.57&new-version=0.10.60)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/DigitalExtinction/Game/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 81d711d8..0d957ebc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4585,9 +4585,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
 
 [[package]]
 name = "openssl"
-version = "0.10.57"
+version = "0.10.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
+checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
 dependencies = [
  "bitflags 2.4.0",
  "cfg-if",
@@ -4617,9 +4617,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.93"
+version = "0.9.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
+checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
 dependencies = [
  "cc",
  "libc",

From fedacc33b0ecb2f0bb512e9d7213c59a9ef9c3c4 Mon Sep 17 00:00:00 2001
From: Martin Indra <martin.indra@mgn.cz>
Date: Wed, 24 Jan 2024 14:43:13 +0100
Subject: [PATCH 15/18] Fix clippy warnings (#781)

```
error: unused import: `Vec4Net`
 --> crates/messages/src/players/mod.rs:5:48
  |
5 | pub use geom::{TransformNet, Vec2Net, Vec3Net, Vec4Net};
  |                                                ^^^^^^^
  |
  = note: `-D unused-imports` implied by `-D warnings`
  = help: to override `-D warnings` add `#[allow(unused_imports)]`

error: unused import: `PathError`
 --> crates/messages/src/players/mod.rs:6:16
  |
6 | pub use path::{PathError, PathNet};
  |                ^^^^^^^^^
```
---
 crates/messages/src/lib.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/crates/messages/src/lib.rs b/crates/messages/src/lib.rs
index 93e3f9a4..07cfa8f8 100644
--- a/crates/messages/src/lib.rs
+++ b/crates/messages/src/lib.rs
@@ -4,7 +4,8 @@
 pub use game::{FromGame, JoinError, Readiness, ToGame};
 pub use players::{
     BorrowedFromPlayers, ChatMessage, ChatMessageError, EntityNet, FromPlayers, HealthDelta,
-    NetEntityIndex, NetProjectile, ToPlayers, MAX_CHAT_LEN,
+    NetEntityIndex, NetProjectile, PathError, PathNet, ToPlayers, TransformNet, Vec2Net, Vec3Net,
+    Vec4Net, MAX_CHAT_LEN,
 };
 pub use server::{FromServer, GameOpenError, ToServer};
 

From 44cea447846b069b43dce0d6800194418188b5f3 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 24 Jan 2024 14:51:04 +0100
Subject: [PATCH 16/18] Bump shlex from 1.2.0 to 1.3.0 (#780)

Bumps [shlex](https://github.com/comex/rust-shlex) from 1.2.0 to 1.3.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/comex/rust-shlex/blob/master/CHANGELOG.md">shlex's
changelog</a>.</em></p>
<blockquote>
<h1>1.3.0</h1>
<ul>
<li>Full fix for the high-severity security vulnerability <a
href="https://rustsec.org/advisories/RUSTSEC-2024-0006.html">RUSTSEC-2024-0006</a>
a.k.a. <a
href="https://github.com/comex/rust-shlex/security/advisories/GHSA-r7qv-8r2h-pg27">GHSA-r7qv-8r2h-pg27</a>:
<ul>
<li>Deprecates quote APIs in favor of <code>try_</code> equivalents that
complain about nul bytes.</li>
<li>Also adds a builder API, which allows re-enabling nul bytes without
using the deprecated interface, and in the future can allow other things
(as discussed in quoting_warning).</li>
<li>Adds documentation about various security risks that remain,
particularly with interactive shells.</li>
</ul>
</li>
<li>Adds explicit MSRV of 1.46.0.</li>
</ul>
<h1>1.2.1</h1>
<ul>
<li>Partial fix for the high-severity security vulnerability <a
href="https://rustsec.org/advisories/RUSTSEC-2024-0006.html">RUSTSEC-2024-0006</a>
a.k.a. <a
href="https://github.com/comex/rust-shlex/security/advisories/GHSA-r7qv-8r2h-pg27">GHSA-r7qv-8r2h-pg27</a>
without bumping MSRV:
<ul>
<li>The bytes <code>{</code> and <code>\xa0</code> are now escaped by
quoting functions.</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/comex/rust-shlex/commits">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=shlex&package-manager=cargo&previous-version=1.2.0&new-version=1.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/DigitalExtinction/Game/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Martin Indra <martin.indra@mgn.cz>
---
 Cargo.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 0d957ebc..58ea36a6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5528,9 +5528,9 @@ dependencies = [
 
 [[package]]
 name = "shlex"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "signal-hook"

From 8a691992d78ecd3b6ed961ba8526a770300846ad Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 24 Jan 2024 14:58:34 +0100
Subject: [PATCH 17/18] Bump h2 from 0.3.21 to 0.3.24 (#779)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Bumps [h2](https://github.com/hyperium/h2) from 0.3.21 to 0.3.24.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/hyperium/h2/releases">h2's
releases</a>.</em></p>
<blockquote>
<h2>v0.3.24</h2>
<h2>Fixed</h2>
<ul>
<li>Limit error resets for misbehaving connections.</li>
</ul>
<h2>v0.3.23</h2>
<h2>What's Changed</h2>
<ul>
<li>cherry-pick fix: streams awaiting capacity lockout in <a
href="https://redirect.github.com/hyperium/h2/pull/734">hyperium/h2#734</a></li>
</ul>
<h2>v0.3.22</h2>
<h2>What's Changed</h2>
<ul>
<li>Add <code>header_table_size(usize)</code> option to client and
server builders.</li>
<li>Improve throughput when vectored IO is not available.</li>
<li>Update indexmap to 2.</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/tottoto"><code>@​tottoto</code></a> made
their first contribution in <a
href="https://redirect.github.com/hyperium/h2/pull/714">hyperium/h2#714</a></li>
<li><a href="https://github.com/xiaoyawei"><code>@​xiaoyawei</code></a>
made their first contribution in <a
href="https://redirect.github.com/hyperium/h2/pull/712">hyperium/h2#712</a></li>
<li><a href="https://github.com/Protryon"><code>@​Protryon</code></a>
made their first contribution in <a
href="https://redirect.github.com/hyperium/h2/pull/719">hyperium/h2#719</a></li>
<li><a href="https://github.com/4JX"><code>@​4JX</code></a> made their
first contribution in <a
href="https://redirect.github.com/hyperium/h2/pull/638">hyperium/h2#638</a></li>
<li><a
href="https://github.com/vuittont60"><code>@​vuittont60</code></a> made
their first contribution in <a
href="https://redirect.github.com/hyperium/h2/pull/724">hyperium/h2#724</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md">h2's
changelog</a>.</em></p>
<blockquote>
<h1>0.3.24 (January 17, 2024)</h1>
<ul>
<li>Limit error resets for misbehaving connections.</li>
</ul>
<h1>0.3.23 (January 10, 2024)</h1>
<ul>
<li>Backport fix from 0.4.1 for stream capacity assignment.</li>
</ul>
<h1>0.3.22 (November 15, 2023)</h1>
<ul>
<li>Add <code>header_table_size(usize)</code> option to client and
server builders.</li>
<li>Improve throughput when vectored IO is not available.</li>
<li>Update indexmap to 2.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/hyperium/h2/commit/7243ab5854b2375213a5a2cdfd543f1d669661e2"><code>7243ab5</code></a>
Prepare v0.3.24</li>
<li><a
href="https://github.com/hyperium/h2/commit/d919cd6fd8e0f4f5d1f6282fab0b38a1b4bf999c"><code>d919cd6</code></a>
streams: limit error resets for misbehaving connections</li>
<li><a
href="https://github.com/hyperium/h2/commit/a7eb14a487c0094187314fca63cfe4de4d3d78ef"><code>a7eb14a</code></a>
v0.3.23</li>
<li><a
href="https://github.com/hyperium/h2/commit/b668c7fbe22e0cb4a76b0a67498cbb4d0aacbc75"><code>b668c7f</code></a>
fix: streams awaiting capacity lockout (<a
href="https://redirect.github.com/hyperium/h2/issues/730">#730</a>) (<a
href="https://redirect.github.com/hyperium/h2/issues/734">#734</a>)</li>
<li><a
href="https://github.com/hyperium/h2/commit/0f412d8b9c8d309966197873ad1d065adc23c794"><code>0f412d8</code></a>
v0.3.22</li>
<li><a
href="https://github.com/hyperium/h2/commit/c7ca62f69b3b16d66f088ed2684f4534a8034c76"><code>c7ca62f</code></a>
docs: fix typos (<a
href="https://redirect.github.com/hyperium/h2/issues/724">#724</a>)</li>
<li><a
href="https://github.com/hyperium/h2/commit/ef743ecb2243786c0573b9fe726290878359689b"><code>ef743ec</code></a>
Add a setter for header_table_size (<a
href="https://redirect.github.com/hyperium/h2/issues/638">#638</a>)</li>
<li><a
href="https://github.com/hyperium/h2/commit/56651e6e513597d105c5df37a5f5937e2ba50be6"><code>56651e6</code></a>
fix lint about unused import</li>
<li><a
href="https://github.com/hyperium/h2/commit/4aa7b163425648926454564aa4116ed6f20f9fee"><code>4aa7b16</code></a>
Fix documentation for max_send_buffer_size (<a
href="https://redirect.github.com/hyperium/h2/issues/718">#718</a>)</li>
<li><a
href="https://github.com/hyperium/h2/commit/d03c54a80dad60a4f23e110eee227d24a413b21e"><code>d03c54a</code></a>
chore(dependencies): update tracing minimal version to 0.1.35</li>
<li>Additional commits viewable in <a
href="https://github.com/hyperium/h2/compare/v0.3.21...v0.3.24">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=h2&package-manager=cargo&previous-version=0.3.21&new-version=0.3.24)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/DigitalExtinction/Game/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 Cargo.lock | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 58ea36a6..638d02b2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3393,9 +3393,9 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.3.21"
+version = "0.3.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
+checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
 dependencies = [
  "bytes",
  "fnv",
@@ -3403,7 +3403,7 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap 1.9.3",
+ "indexmap 2.0.2",
  "slab",
  "tokio",
  "tokio-util",
@@ -6610,7 +6610,7 @@ dependencies = [
  "js-sys",
  "log",
  "naga",
- "parking_lot 0.12.1",
+ "parking_lot 0.11.2",
  "profiling",
  "raw-window-handle",
  "smallvec",
@@ -6635,7 +6635,7 @@ dependencies = [
  "codespan-reporting",
  "log",
  "naga",
- "parking_lot 0.12.1",
+ "parking_lot 0.11.2",
  "profiling",
  "raw-window-handle",
  "rustc-hash",
@@ -6674,7 +6674,7 @@ dependencies = [
  "metal",
  "naga",
  "objc",
- "parking_lot 0.12.1",
+ "parking_lot 0.11.2",
  "profiling",
  "range-alloc",
  "raw-window-handle",

From 0e42f6430851db488d63e844e2d27ff7c96ff7f6 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 24 Jan 2024 15:33:03 +0100
Subject: [PATCH 18/18] Bump unsafe-libyaml from 0.2.9 to 0.2.10 (#778)

---
 Cargo.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 638d02b2..b43a32a2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6373,9 +6373,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 
 [[package]]
 name = "unsafe-libyaml"
-version = "0.2.9"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
+checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
 
 [[package]]
 name = "untrusted"