~starkingdoms/starkingdoms

011a026fb2c7d43d688798bdeb8ea70a6315b7b3 — core 6 days ago a19c223
netcode: rd3
M Cargo.lock => Cargo.lock +1131 -12
@@ 86,6 86,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"

[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
 "crypto-common",
 "generic-array",
]

[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 226,6 236,45 @@ dependencies = [
]

[[package]]
name = "asn1-rs"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
dependencies = [
 "asn1-rs-derive",
 "asn1-rs-impl",
 "displaydoc",
 "nom",
 "num-traits",
 "rusticata-macros",
 "thiserror 2.0.18",
 "time",
]

[[package]]
name = "asn1-rs-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
 "synstructure",
]

[[package]]
name = "asn1-rs-impl"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
]

[[package]]
name = "assert_type_match"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 389,6 438,34 @@ dependencies = [
]

[[package]]
name = "aws-lc-rs"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
dependencies = [
 "aws-lc-sys",
 "zeroize",
]

[[package]]
name = "aws-lc-sys"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
dependencies = [
 "cc",
 "cmake",
 "dunce",
 "fs_extra",
]

[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"

[[package]]
name = "bevy"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 463,7 540,7 @@ dependencies = [
 "downcast-rs 2.0.2",
 "log",
 "thiserror 2.0.18",
 "variadics_please",
 "variadics_please 1.1.0",
 "wasm-bindgen",
 "web-sys",
]


@@ 703,7 780,7 @@ dependencies = [
 "smallvec",
 "subsecond",
 "thiserror 2.0.18",
 "variadics_please",
 "variadics_please 1.1.0",
]

[[package]]


@@ 1023,7 1100,7 @@ dependencies = [
 "rand_distr",
 "serde",
 "thiserror 2.0.18",
 "variadics_please",
 "variadics_please 1.1.0",
]

[[package]]


@@ 1199,7 1276,7 @@ dependencies = [
 "smol_str",
 "thiserror 2.0.18",
 "uuid",
 "variadics_please",
 "variadics_please 1.1.0",
 "wgpu-types",
]



@@ 1261,7 1338,7 @@ dependencies = [
 "smallvec",
 "thiserror 2.0.18",
 "tracing",
 "variadics_please",
 "variadics_please 1.1.0",
 "wasm-bindgen",
 "web-sys",
 "wgpu",


@@ 1280,10 1357,23 @@ dependencies = [
]

[[package]]
name = "bevy_renet2"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76cbba6aff2d461ccce2bb3a62fa328a2f39aeb6d61a37750c098396b7a6c242"
dependencies = [
 "bevy_app",
 "bevy_ecs",
 "bevy_time",
 "renet2",
 "renet2_netcode",
]

[[package]]
name = "bevy_replicon"
version = "0.39.5"
version = "0.40.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a0d6f7d37938f1f6797950774a41adc189a459d73cffb1941804a5d9d8fd059"
checksum = "6112b46715b14666a32ed1e39fb8074f2126936c77e829c9d24c2199dd12a8d0"
dependencies = [
 "bevy",
 "bitflags 2.11.1",


@@ 1296,11 1386,22 @@ dependencies = [
 "serde",
 "smallbitvec",
 "typeid",
 "variadics_please",
 "variadics_please 2.0.0",
 "xxhash-rust",
]

[[package]]
name = "bevy_replicon_renet2"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc45af0d5e0d365b1600b0ba2a3e57c9b2180618915afb74a7f1c7524ce90e6"
dependencies = [
 "bevy",
 "bevy_renet2",
 "bevy_replicon",
]

[[package]]
name = "bevy_scene"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1409,7 1510,7 @@ dependencies = [
 "bevy_state_macros",
 "bevy_utils",
 "log",
 "variadics_please",
 "variadics_please 1.1.0",
]

[[package]]


@@ 1861,6 1962,17 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"

[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
 "cfg-if",
 "cipher",
 "cpufeatures 0.2.17",
]

[[package]]
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"


@@ 1871,6 1983,19 @@ dependencies = [
]

[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
 "aead",
 "chacha20 0.9.1",
 "cipher",
 "poly1305",
 "zeroize",
]

[[package]]
name = "chiapos-chacha8"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1907,6 2032,17 @@ dependencies = [
]

[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
 "crypto-common",
 "inout",
 "zeroize",
]

[[package]]
name = "clap"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1952,6 2088,15 @@ dependencies = [
]

[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
 "cc",
]

[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2240,6 2385,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"

[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
 "crossbeam-channel",
 "crossbeam-deque",
 "crossbeam-epoch",
 "crossbeam-queue",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2295,6 2453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
 "generic-array",
 "rand_core 0.6.4",
 "typenum",
]



@@ 2357,6 2516,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"

[[package]]
name = "der-parser"
version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
dependencies = [
 "asn1-rs",
 "displaydoc",
 "nom",
 "num-bigint",
 "num-traits",
 "rusticata-macros",
]

[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
 "powerfmt",
]

[[package]]
name = "derive_builder"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2524,6 2706,17 @@ dependencies = [
]

[[package]]
name = "displaydoc"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
]

[[package]]
name = "disqualified"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2566,6 2759,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"

[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"

[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2842,6 3041,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"

[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
 "percent-encoding",
]

[[package]]
name = "fragile"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9"
dependencies = [
 "futures-core",
]

[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"

[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2851,12 3074,28 @@ dependencies = [
]

[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
 "futures-channel",
 "futures-core",
 "futures-executor",
 "futures-io",
 "futures-sink",
 "futures-task",
 "futures-util",
]

[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
 "futures-core",
 "futures-sink",
]

[[package]]


@@ 2866,6 3105,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"

[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
 "futures-core",
 "futures-task",
 "futures-util",
]

[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2896,6 3146,12 @@ dependencies = [
]

[[package]]
name = "futures-sink"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"

[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2907,9 3163,13 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
 "futures-channel",
 "futures-core",
 "futures-io",
 "futures-macro",
 "futures-sink",
 "futures-task",
 "memchr",
 "pin-project-lite",
 "slab",
]


@@ 2946,14 3206,29 @@ dependencies = [

[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
 "cfg-if",
 "js-sys",
 "libc",
 "wasi",
 "wasm-bindgen",
]

[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
 "cfg-if",
 "js-sys",
 "libc",
 "r-efi 5.3.0",
 "wasip2",
 "wasm-bindgen",
]

[[package]]


@@ 3266,6 3541,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"

[[package]]
name = "hmac-sha256"
version = "1.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f"

[[package]]
name = "httlib-huffman"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877"

[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3282,6 3569,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"

[[package]]
name = "icu_collections"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
 "displaydoc",
 "potential_utf",
 "utf8_iter",
 "yoke",
 "zerofrom",
 "zerovec",
]

[[package]]
name = "icu_locale_core"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
 "displaydoc",
 "litemap",
 "tinystr",
 "writeable",
 "zerovec",
]

[[package]]
name = "icu_normalizer"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
 "icu_collections",
 "icu_normalizer_data",
 "icu_properties",
 "icu_provider",
 "smallvec",
 "zerovec",
]

[[package]]
name = "icu_normalizer_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"

[[package]]
name = "icu_properties"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
 "icu_collections",
 "icu_locale_core",
 "icu_properties_data",
 "icu_provider",
 "zerotrie",
 "zerovec",
]

[[package]]
name = "icu_properties_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"

[[package]]
name = "icu_provider"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
 "displaydoc",
 "icu_locale_core",
 "writeable",
 "yoke",
 "zerofrom",
 "zerotrie",
 "zerovec",
]

[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3294,6 3663,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"

[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
 "idna_adapter",
 "smallvec",
 "utf8_iter",
]

[[package]]
name = "idna_adapter"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
 "icu_normalizer",
 "icu_properties",
]

[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3339,6 3729,15 @@ dependencies = [
]

[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
 "generic-array",
]

[[package]]
name = "inventory"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3642,6 4041,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"

[[package]]
name = "litemap"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"

[[package]]
name = "litrs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3669,8 4074,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86"

[[package]]
name = "malloc_buf"
version = "0.0.6"
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"

[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [


@@ 3726,6 4137,12 @@ dependencies = [
]

[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"

[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3758,6 4175,12 @@ dependencies = [
]

[[package]]
name = "mutants"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126"

[[package]]
name = "naga"
version = "27.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3862,6 4285,16 @@ dependencies = [
]

[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
 "memchr",
 "minimal-lexical",
]

[[package]]
name = "nonmax"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3917,6 4350,16 @@ dependencies = [
]

[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
 "num-integer",
 "num-traits",
]

[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3926,6 4369,12 @@ dependencies = [
]

[[package]]
name = "num-conv"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"

[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3937,6 4386,15 @@ dependencies = [
]

[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
 "num-traits",
]

[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4224,6 4682,12 @@ dependencies = [
]

[[package]]
name = "octets"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7"

[[package]]
name = "offset-allocator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4234,6 4698,15 @@ dependencies = [
]

[[package]]
name = "oid-registry"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
dependencies = [
 "asn1-rs",
]

[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4246,6 4719,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"

[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"

[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"

[[package]]
name = "orbclient"
version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4375,6 4860,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"

[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
 "base64",
 "serde_core",
]

[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4503,6 4998,17 @@ dependencies = [
]

[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
 "cpufeatures 0.2.17",
 "opaque-debug",
 "universal-hash",
]

[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4540,6 5046,21 @@ dependencies = [
]

[[package]]
name = "potential_utf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
dependencies = [
 "zerovec",
]

[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"

[[package]]
name = "pp-rs"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4644,6 5165,61 @@ dependencies = [
]

[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
 "bytes",
 "cfg_aliases",
 "pin-project-lite",
 "quinn-proto",
 "quinn-udp",
 "rustc-hash 2.1.2",
 "rustls",
 "socket2 0.6.4",
 "thiserror 2.0.18",
 "tokio",
 "tracing",
 "web-time 1.1.0",
]

[[package]]
name = "quinn-proto"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
 "bytes",
 "getrandom 0.3.4",
 "lru-slab",
 "rand 0.9.4",
 "ring",
 "rustc-hash 2.1.2",
 "rustls",
 "rustls-pki-types",
 "slab",
 "thiserror 2.0.18",
 "tinyvec",
 "tracing",
 "web-time 1.1.0",
]

[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
 "cfg_aliases",
 "libc",
 "once_cell",
 "socket2 0.6.4",
 "tracing",
 "windows-sys 0.60.2",
]

[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4686,7 5262,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [
 "chacha20",
 "chacha20 0.10.0",
 "getrandom 0.4.2",
 "rand_core 0.10.1",
]


@@ 4703,6 5279,15 @@ dependencies = [

[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
 "getrandom 0.2.17",
]

[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"


@@ 4765,6 5350,19 @@ dependencies = [
]

[[package]]
name = "rcgen"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
dependencies = [
 "pem",
 "ring",
 "rustls-pki-types",
 "time",
 "yasna",
]

[[package]]
name = "rdst"
version = "0.20.14"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4868,6 5466,77 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"

[[package]]
name = "renet2"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "689381686b0760f174e973d038df220eec2834d64345c2b609212d40679dc274"
dependencies = [
 "bevy_ecs",
 "bytes",
 "log",
 "octets",
]

[[package]]
name = "renet2_netcode"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bda5b3ebbfacba2976a9825dac8cd57efadc2a55a98fc334fb504fa714d1200"
dependencies = [
 "anyhow",
 "async-channel",
 "bevy_ecs",
 "bytes",
 "crossbeam",
 "fragile",
 "futures",
 "hmac-sha256",
 "http",
 "js-sys",
 "log",
 "octets",
 "quinn",
 "rcgen",
 "renet2",
 "renetcode2",
 "rustls",
 "rustls-pki-types",
 "send_wrapper",
 "time",
 "tokio",
 "url",
 "urlencoding",
 "wasm-bindgen",
 "wasm-bindgen-futures",
 "web-sys",
 "wtransport",
]

[[package]]
name = "renetcode2"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b908e79c2c22d075bf4307a36f96bff3ebfedaba40b377d70b2c2e899e6a53c"
dependencies = [
 "chacha20poly1305",
 "log",
]

[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
 "cc",
 "cfg-if",
 "getrandom 0.2.17",
 "libc",
 "untrusted",
 "windows-sys 0.52.0",
]

[[package]]
name = "robust"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4915,6 5584,15 @@ dependencies = [
]

[[package]]
name = "rusticata-macros"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
 "nom",
]

[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4941,6 5619,65 @@ dependencies = [
]

[[package]]
name = "rustls"
version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
 "aws-lc-rs",
 "log",
 "once_cell",
 "ring",
 "rustls-pki-types",
 "rustls-webpki",
 "subtle",
 "zeroize",
]

[[package]]
name = "rustls-native-certs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
 "openssl-probe",
 "rustls-pki-types",
 "schannel",
 "security-framework",
]

[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
 "rustls-pki-types",
]

[[package]]
name = "rustls-pki-types"
version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [
 "web-time 1.1.0",
 "zeroize",
]

[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
 "aws-lc-rs",
 "ring",
 "rustls-pki-types",
 "untrusted",
]

[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4974,6 5711,15 @@ dependencies = [
]

[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
 "windows-sys 0.61.2",
]

[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 4999,6 5745,29 @@ dependencies = [
]

[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
 "bitflags 2.11.1",
 "core-foundation 0.10.1",
 "core-foundation-sys",
 "libc",
 "security-framework-sys",
]

[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
 "core-foundation-sys",
 "libc",
]

[[package]]
name = "self_cell"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5015,6 5784,9 @@ name = "send_wrapper"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
dependencies = [
 "futures-core",
]

[[package]]
name = "serde"


@@ 5108,6 5880,17 @@ dependencies = [
]

[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
 "cfg-if",
 "cpufeatures 0.2.17",
 "digest",
]

[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5123,6 5906,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"

[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
 "errno",
 "libc",
]

[[package]]
name = "simba"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5244,6 6037,26 @@ dependencies = [
]

[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
 "libc",
 "windows-sys 0.52.0",
]

[[package]]
name = "socket2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [
 "libc",
 "windows-sys 0.61.2",
]

[[package]]
name = "spade"
version = "2.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5297,17 6110,24 @@ dependencies = [
 "bevy",
 "bevy_common_assets",
 "bevy_replicon",
 "bevy_replicon_renet2",
 "console_error_panic_hook",
 "ctrlc",
 "getrandom 0.2.17",
 "getrandom 0.4.2",
 "good_lp",
 "leafwing-input-manager",
 "pico-args",
 "rand 0.10.1",
 "serde",
 "serde_json",
 "tokio",
 "tracing-subscriber",
 "tracing-web",
 "url",
 "wasm-bindgen",
 "wasm-bindgen-futures",
 "web-sys",
 "web-time 1.1.0",
]



@@ 5358,6 6178,12 @@ dependencies = [
]

[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"

[[package]]
name = "svg_fmt"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5397,6 6223,17 @@ dependencies = [
]

[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
]

[[package]]
name = "sys-locale"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5508,6 6345,37 @@ dependencies = [
]

[[package]]
name = "time"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
 "deranged",
 "itoa",
 "num-conv",
 "powerfmt",
 "serde_core",
 "time-core",
 "time-macros",
]

[[package]]
name = "time-core"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"

[[package]]
name = "time-macros"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
 "num-conv",
 "time-core",
]

[[package]]
name = "tiny-skia"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5533,6 6401,16 @@ dependencies = [
]

[[package]]
name = "tinystr"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [
 "displaydoc",
 "zerovec",
]

[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5558,6 6436,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"

[[package]]
name = "tokio"
version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
 "bytes",
 "libc",
 "mio",
 "parking_lot",
 "pin-project-lite",
 "signal-hook-registry",
 "socket2 0.6.4",
 "tokio-macros",
 "windows-sys 0.61.2",
]

[[package]]
name = "tokio-macros"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
]

[[package]]
name = "toml"
version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5819,12 6725,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"

[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
 "crypto-common",
 "subtle",
]

[[package]]
name = "unsynn"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501a7adf1a4bd9951501e5c66621e972ef8874d787628b7f90e64f936ef7ec0a"
dependencies = [
 "mutants",
 "proc-macro2",
 "rustc-hash 2.1.2",
]

[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"

[[package]]
name = "url"
version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
 "form_urlencoded",
 "idna",
 "percent-encoding",
 "serde",
]

[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"

[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"

[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"

[[package]]
name = "uuid"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5854,6 6811,16 @@ dependencies = [
]

[[package]]
name = "variadics_please"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b53d0f7f2d0182759a549f1f313ce16b07d8f9ba93098157b5fa10ee3e61117f"
dependencies = [
 "quote",
 "unsynn",
]

[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6919,6 7886,49 @@ dependencies = [
]

[[package]]
name = "writeable"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"

[[package]]
name = "wtransport"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5e745c8789c20095c9061d292098d4106660efe2d172efd8ae7a369fe28e3e"
dependencies = [
 "bytes",
 "pem",
 "quinn",
 "rcgen",
 "rustls",
 "rustls-native-certs",
 "rustls-pemfile",
 "rustls-pki-types",
 "sha2",
 "socket2 0.5.10",
 "thiserror 2.0.18",
 "time",
 "tokio",
 "tracing",
 "url",
 "wtransport-proto",
 "x509-parser",
]

[[package]]
name = "wtransport-proto"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a09d89a8dba201c2439d9d5eca55a0faa08909d69da50decdb5ec00be0ac504"
dependencies = [
 "httlib-huffman",
 "octets",
 "thiserror 2.0.18",
 "url",
]

[[package]]
name = "x11-dl"
version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6951,6 7961,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"

[[package]]
name = "x509-parser"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460"
dependencies = [
 "asn1-rs",
 "data-encoding",
 "der-parser",
 "lazy_static",
 "nom",
 "oid-registry",
 "rusticata-macros",
 "thiserror 2.0.18",
 "time",
]

[[package]]
name = "xcursor"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6996,12 8023,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"

[[package]]
name = "yasna"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
 "time",
]

[[package]]
name = "yazi"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5"

[[package]]
name = "yoke"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
 "stable_deref_trait",
 "yoke-derive",
 "zerofrom",
]

[[package]]
name = "yoke-derive"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
 "synstructure",
]

[[package]]
name = "zeno"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 7028,6 8087,66 @@ dependencies = [
]

[[package]]
name = "zerofrom"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
 "zerofrom-derive",
]

[[package]]
name = "zerofrom-derive"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
 "synstructure",
]

[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"

[[package]]
name = "zerotrie"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
 "displaydoc",
 "yoke",
 "zerofrom",
]

[[package]]
name = "zerovec"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
 "yoke",
 "zerofrom",
 "zerovec-derive",
]

[[package]]
name = "zerovec-derive"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.117",
]

[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"

M Cargo.toml => Cargo.toml +6 -5
@@ 62,11 62,12 @@ console_error_panic_hook = "0.1"
test_each_file = "0.3.7"
colored = "3"
qsv-tabwriter = "2"
aeronet = { version = "0.20" }
aeronet_websocket = { version = "0.20" }
aeronet_transport = { version = "0.20" }
aeronet_replicon = { version = "0.20" }
bevy_replicon = { version = "0.39"}
bevy_replicon = { version = "0.40" }
bevy_replicon_renet2 = { version = "0.17", default-features = false }
url = "2"
serde_json = "1"
web-sys = "0.3"
wasm-bindgen-futures = "0.4"

[profile.dev]
opt-level = 1

M crates/unified/Cargo.toml => crates/unified/Cargo.toml +12 -2
@@ 18,14 18,21 @@ leafwing-input-manager = { workspace = true }
good_lp = { workspace = true }
web-time = { workspace = true }
bevy_replicon = { workspace = true, features = ["client"] }
bevy_replicon_renet2 = { workspace = true, features = ["client", "netcode"] }
url = { workspace = true }
serde_json = { workspace = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
ctrlc = { workspace = true }
tokio = { version = "1", features = ["rt-multi-thread"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = { workspace = true }
tracing-web = { workspace = true }
console_error_panic_hook = { workspace = true }
getrandom02 = { package = "getrandom", version = "0.2", features = ["js"] }
web-sys = { workspace = true, features = ["Window", "Response"] }

[features]
native_dev = [


@@ 37,6 44,9 @@ native_dev = [
native = [
    "bevy/x11",
    "bevy/wayland",
    "bevy_replicon/server"
    "bevy_replicon/server",
    "bevy_replicon_renet2/server",
    "bevy_replicon_renet2/native_transport",
    "bevy_replicon_renet2/wt_server_transport"
]
wasm = ["getrandom/wasm_js", "bevy/webgl2"]
wasm = ["getrandom/wasm_js", "bevy/webgl2", "bevy_replicon_renet2/wt_client_transport"]

M crates/unified/assets/config/world.wc.toml => crates/unified/assets/config/world.wc.toml +1 -0
@@ 4,6 4,7 @@ gravity = 500.0
gravity_iterations = 8
spawn_parts_at = "Earth"
spawn_parts_interval_secs = 1
orbit_scale_factor = 4.0

[part]
default_height = 50

M crates/unified/src/cli.rs => crates/unified/src/cli.rs +11 -5
@@ 9,7 9,9 @@ pub enum StkArgs {
    },
    #[cfg(not(target_arch = "wasm32"))]
    Server {
        bind_to: std::net::SocketAddr,
        management_bind: std::net::SocketAddr,
        native_bind: std::net::SocketAddr,
        wt_bind: std::net::SocketAddr,
        with_client: bool
    },
}


@@ 43,7 45,9 @@ pub fn parse_args() -> StkArgs {
        #[cfg(not(target_arch = "wasm32"))]
        "server" => {
            StkArgs::Server {
                bind_to: pargs.value_from_str(["-b", "--bind-to"]).unwrap(),
                management_bind: pargs.value_from_str("--management-bind").unwrap(),
                native_bind: pargs.value_from_str("--native-bind").unwrap(),
                wt_bind: pargs.value_from_str("--wt-bind").unwrap(),
                with_client: pargs.contains("--with-client"),
            }
        },


@@ 77,13 81,15 @@ SUBCOMMANDS:

        println!("\
CLIENT:
    -s, --server <URL>      WebSocket URL to connect to
    -s, --server <URL>           Connection string of the server's management port (e.g. http://127.0.0.1:5150/)
        ");
    if cfg!(not(target_arch = "wasm32")) {
        print!("\
SERVER:
    -b, --bind-to <IP:PORT> Socket address to bind to
        --with-client       Start a client connected to this server
    --management-bind <IP:PORT>  Socket address to bind the management port to
    --native-bind <IP:PORT>      Socket address to bind the native (UDP) port to
    --wt-bind <IP:PORT>          Socket address to bind the WebTransport port to
    --with-client                Start a client connected to this server
    \n");
    }
}
\ No newline at end of file

A crates/unified/src/client/interpolation.rs => crates/unified/src/client/interpolation.rs +69 -0
@@ 0,0 1,69 @@
use std::time::Duration;
use bevy_replicon::client::confirm_history::ConfirmHistory;
use bevy_replicon::prelude::RepliconTick;
use web_time::Instant;
use crate::prelude::*;
use crate::shared::plugins::TICK_RATE;

const MAX_INTERPOLATION_DURATION: Duration = Duration::from_millis(500);

#[derive(Component)]
pub struct TransformInterpolation {
    start: Transform,
    end: Transform,
    end_tick: RepliconTick,
    duration: Duration,
    end_received_at: Instant,
}

impl TransformInterpolation {
    pub fn new(transform: Transform, tick: RepliconTick) -> Self {
        Self {
            start: transform,
            end: transform,
            end_tick: tick,
            duration: Duration::ZERO,
            end_received_at: Instant::now(),
        }
    }
}

pub fn transform_interpolation_plugin(app: &mut App) {
    app.add_systems(Update, (record_transform_changes, apply_transform_interpolation).chain());
}

// must run before `apply_transform_interpolation`
fn record_transform_changes(
    mut entities: Query<(&Transform, &ConfirmHistory, &mut TransformInterpolation), Changed<Transform>>,
) {
    let now = Instant::now();
    for (transform, confirm_history, mut interpolation) in &mut entities {
        let tick = confirm_history.last_tick();
        let tick_delta = tick - interpolation.end_tick;
        let duration = Duration::from_secs_f64(tick_delta as f64 / TICK_RATE).min(MAX_INTERPOLATION_DURATION);

        interpolation.start = interpolation.end;
        interpolation.end = *transform;
        interpolation.end_tick = tick;
        interpolation.duration = duration;
        interpolation.end_received_at = now;
    }
}

fn apply_transform_interpolation(mut entities: Query<(&mut Transform, &TransformInterpolation)>) {
    let now = Instant::now();
    for (mut transform, interpolation) in &mut entities {
        let t = if interpolation.duration > Duration::ZERO {
            (now.duration_since(interpolation.end_received_at).as_secs_f64() / interpolation.duration.as_secs_f64())
                .clamp(0.0, 1.0) as f32
        } else {
            1.0
        };

        *transform.bypass_change_detection() = Transform {
            translation: interpolation.start.translation.lerp(interpolation.end.translation, t),
            rotation: interpolation.start.rotation.slerp(interpolation.end.rotation, t),
            scale: interpolation.start.scale.lerp(interpolation.end.scale, t),
        };
    }
}

M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +162 -48
@@ 15,11 15,37 @@ use planet::incoming_planets::incoming_planets_plugin;
use crate::client::components::Me;
use crate::client::ship::attachment::client_attachment_plugin;
use crate::shared::ecs::{GameplayState, TimeOffset};
use crate::shared::gravity::update_gravity;
use crate::shared::net::Hi;
use crate::shared::orbit::OrbitPlugin;
use crate::shared::net::{parse_management_address, ManagementInfo, Hi, STARKINGDOMS_PROTOCOL_MAGIC};
use bevy_replicon::prelude::RepliconChannels;
use bevy_replicon_renet2::RenetChannelsExt;
use bevy_replicon_renet2::netcode::{ClientAuthentication, NetcodeClientTransport};
use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetClient};
use std::net::{IpAddr, SocketAddr};
use web_time::SystemTime;

#[cfg(not(target_arch = "wasm32"))]
use bevy_replicon_renet2::netcode::NativeSocket;
#[cfg(not(target_arch = "wasm32"))]
use std::io::{Read, Write};
#[cfg(not(target_arch = "wasm32"))]
use std::net::{TcpStream, UdpSocket};

#[cfg(target_arch = "wasm32")]
use bevy_replicon_renet2::netcode::{ServerCertHash, WebServerDestination, WebTransportClient, WebTransportClientConfig};
#[cfg(target_arch = "wasm32")]
use crate::shared::net::decode_cert_hash;
#[cfg(target_arch = "wasm32")]
use std::cell::RefCell;
#[cfg(target_arch = "wasm32")]
use std::rc::Rc;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::JsFuture;
use crate::shared::config::planet::Planet;

pub mod colors;
pub mod interpolation;
pub mod key_input;
pub mod parts;
pub mod planet;


@@ 43,12 69,11 @@ impl Plugin for ClientPlugin {
        app
            .init_gizmo_group::<StarguideGizmos>()
            .add_plugins(rendering::render_plugin)
            .add_plugins(OrbitPlugin)
            .add_systems(FixedUpdate, update_gravity.before(PhysicsSystems::Prepare))
            .add_plugins(input::input_plugin)
            .add_plugins(ship::thrusters::client_thrusters_plugin)
            .add_plugins((incoming_planets_plugin, indicators_plugin))
            .add_plugins(parts_plugin)
            .add_plugins(interpolation::transform_interpolation_plugin)
            .add_plugins(key_input_plugin)
            .add_plugins(starfield_plugin)
            .add_plugins(ui_plugin)


@@ 56,7 81,7 @@ impl Plugin for ClientPlugin {
            .add_plugins(client_attachment_plugin)
            .add_plugins(starguide_init_plugin)
            .add_plugins(starguide_input_plugin)
            .add_plugins(starguide_orbit_plugin)
            //.add_plugins(starguide_orbit_plugin)
            .add_plugins(crafting_ui_plugin)
            .add_systems(Update, find_me)
            .insert_state(GameplayState::Main)


@@ 64,56 89,145 @@ impl Plugin for ClientPlugin {
            .insert_resource(TimeOffset::default());

        let server = self.server.clone();
        app.add_systems(PostStartup, move |mut commands: Commands| {
            /*#[cfg(target_arch = "wasm32")]
            let config = ClientConfig {};
            #[cfg(not(target_arch = "wasm32"))]
            let config = ClientConfig::builder().with_no_cert_validation();

        #[cfg(not(target_arch = "wasm32"))]
        app.add_systems(PostStartup, move |mut commands: Commands, channels: Res<RepliconChannels>| {
            let Some(server) = server.as_ref() else { return };
            commands.spawn((Name::new("default-session"), TransportConfig { max_memory_usage: 536_870_912, ..default() }, AeronetRepliconClient))
                .queue(
                    WebSocketClient::connect(config, server.clone()));*/
            let management_addr = parse_management_address(server).expect("invalid server connection string");
            let info = fetch_management_info(management_addr);

            let (client, transport) = build_client(&channels, management_addr.ip(), &info);
            commands.insert_resource(client);
            commands.insert_resource(transport);
        });
        /*
        if self.server.is_some() {
            app.add_observer(on_connecting);
            app.add_observer(on_connected);
            app.add_observer(on_disconnected);
        }*/

        #[cfg(target_arch = "wasm32")]
        {
            app.add_systems(PostStartup, move || {
                let Some(server) = server.as_ref() else { return };
                let management_addr = parse_management_address(server).expect("invalid server connection string");

                let info = Rc::new(RefCell::new(None));
                let info_handle = info.clone();
                wasm_bindgen_futures::spawn_local(async move {
                    *info_handle.borrow_mut() = Some(fetch_management_info(management_addr).await);
                });

                PENDING_SERVER_INFO.with(|pending| {
                    *pending.borrow_mut() = Some(PendingServerInfo { addr: management_addr, info });
                });
            });
            app.add_systems(Update, connect_when_ready);
        }
    }
}

/*
pub fn on_connecting(
    trigger: On<Add, SessionEndpoint>,
    names: Query<&Name>,
    mut commands: Commands,
) {
    let entity = trigger.event_target();
    let name = names.get(entity).unwrap();
    info!("{name} is connecting");

fn build_client(channels: &RepliconChannels, host: IpAddr, info: &ManagementInfo) -> (RenetClient, NetcodeClientTransport) {
    let connection_config = ConnectionConfig::from_channels(channels.server_configs(), channels.client_configs());
    let client = RenetClient::new(connection_config, false);

    let client_id = rand::random::<u64>();
    let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();

    #[cfg(not(target_arch = "wasm32"))]
    let (socket_id, server_addr, transport) = {
        let bind_addr = if host.is_ipv6() { "[::]:0" } else { "0.0.0.0:0" };
        let socket = NativeSocket::new(UdpSocket::bind(bind_addr).expect("failed to bind client UDP socket"))
            .expect("failed to create native socket");
        let socket_id = 0;
        let server_addr = SocketAddr::new(host, info.native_port);
        let authentication = ClientAuthentication::Unsecure {
            protocol_id: STARKINGDOMS_PROTOCOL_MAGIC,
            client_id,
            socket_id,
            server_addr,
            user_data: None,
        };
        let transport = NetcodeClientTransport::new(current_time, authentication, socket)
            .expect("failed to create client transport");
        (socket_id, server_addr, transport)
    };

    #[cfg(target_arch = "wasm32")]
    let (socket_id, server_addr, transport) = {
        let server_addr = SocketAddr::new(host, info.wt_port);
        let cert_hash = ServerCertHash { hash: decode_cert_hash(&info.cert_hash).expect("invalid cert_hash") };
        let socket = WebTransportClient::new(WebTransportClientConfig::new_with_certs(
            WebServerDestination::Addr(server_addr),
            vec![cert_hash],
        ));
        let socket_id = 1;
        let authentication = ClientAuthentication::Unsecure {
            protocol_id: STARKINGDOMS_PROTOCOL_MAGIC,
            client_id,
            socket_id,
            server_addr,
            user_data: None,
        };
        let transport = NetcodeClientTransport::new(current_time, authentication, socket)
            .expect("failed to create client transport");
        (socket_id, server_addr, transport)
    };

    info!(?socket_id, ?server_addr, "connecting to server");
    (client, transport)
}
pub fn on_connected(trigger: On<Add, Session>, names: Query<&Name>) {
    let entity = trigger.event_target();
    let name = names.get(entity).unwrap();
    info!("{name} is connected");

#[cfg(not(target_arch = "wasm32"))]
fn fetch_management_info(addr: SocketAddr) -> ManagementInfo {
    let mut stream = TcpStream::connect(addr).expect("failed to connect to management port");
    stream.write_all(format!("GET / HTTP/1.1\r\nHost: {addr}\r\nConnection: close\r\n\r\n").as_bytes())
        .expect("failed to send management request");

    let mut response = Vec::new();
    stream.read_to_end(&mut response).expect("failed to read management response");

    let body_start = response.windows(4).position(|w| w == b"\r\n\r\n")
        .map(|i| i + 4)
        .expect("malformed management response");
    serde_json::from_slice(&response[body_start..]).expect("invalid management response body")
}
pub fn on_disconnected(trigger: On<Disconnected>, names: Query<&Name>) {
    let session = trigger.event_target();
    let name = names.get(session).unwrap();

    match &trigger.reason {
        DisconnectReason::ByUser(reason) => {
            info!(?name, ?reason, "session disconnected by user");
        }
        DisconnectReason::ByPeer(reason) => {
            info!(?name, ?reason, "session disconnected by peer");
        }
        DisconnectReason::ByError(err) => {
            warn!(?name, "session disconnected due to error: {err:?}");
        }
    }
}*/
#[cfg(target_arch = "wasm32")]
async fn fetch_management_info(addr: SocketAddr) -> ManagementInfo {
    let window = web_sys::window().expect("no window");
    let response = JsFuture::from(window.fetch_with_str(&format!("http://{addr}/")))
        .await
        .expect("management fetch failed");
    let response: web_sys::Response = response.dyn_into().expect("fetch did not return a Response");
    let text = JsFuture::from(response.text().expect("failed to read response body"))
        .await
        .expect("failed to read response body");
    let text = text.as_string().expect("response body was not a string");
    serde_json::from_str(&text).expect("invalid management response body")
}

#[cfg(target_arch = "wasm32")]
struct PendingServerInfo {
    addr: SocketAddr,
    info: Rc<RefCell<Option<ManagementInfo>>>,
}

#[cfg(target_arch = "wasm32")]
thread_local! {
    static PENDING_SERVER_INFO: RefCell<Option<PendingServerInfo>> = RefCell::new(None);
}

#[cfg(target_arch = "wasm32")]
fn connect_when_ready(mut commands: Commands, channels: Res<RepliconChannels>) {
    let ready = PENDING_SERVER_INFO.with(|pending| {
        let mut pending = pending.borrow_mut();
        let info = pending.as_ref()?.info.borrow_mut().take()?;
        let addr = pending.take().unwrap().addr;
        Some((addr, info))
    });
    let Some((addr, info)) = ready else { return };

    let (client, transport) = build_client(&channels, addr.ip(), &info);
    commands.insert_resource(client);
    commands.insert_resource(transport);
}

pub fn find_me(
    mut msgs: MessageReader<Hi>,

M crates/unified/src/client/parts.rs => crates/unified/src/client/parts.rs +5 -2
@@ 8,8 8,10 @@ use crate::shared::ecs::{DragAction, DragRequestEvent, Part, MAIN_LAYER};
use crate::client::input::CursorWorldCoordinates;
use bevy::color::palettes::css::{ORANGE, PURPLE, RED, YELLOW};
use crate::client::components::Me;
use crate::client::interpolation::TransformInterpolation;
use crate::client::ship::attachment::AttachmentDebugRes;
use crate::prelude::*;
use bevy_replicon::client::confirm_history::ConfirmHistory;

pub fn parts_plugin(app: &mut App) {
    app.insert_resource(DragResource { dragged: None });


@@ 48,16 50,17 @@ fn build_part_sprite(part: &Part, temperature: &Temperature, is_connected: bool,

fn handle_incoming_parts(
    mut commands: Commands,
    new_parts: Query<(Entity, &Part, &Temperature, Option<&PartInShip>), Added<Part>>,
    new_parts: Query<(Entity, &Part, &Temperature, Option<&PartInShip>, &Transform, &ConfirmHistory), Added<Part>>,
    asset_server: Res<AssetServer>,
) {
    for (new_entity, new_part, temperature, is_connected) in new_parts.iter() {
    for (new_entity, new_part, temperature, is_connected, transform, confirm_history) in new_parts.iter() {
        let sprite = build_part_sprite(new_part, temperature, is_connected.is_some(), &asset_server);
        commands
            .entity(new_entity)
            .insert(MAIN_LAYER)
            .insert(sprite)
            .insert(Pickable::default())
            .insert(TransformInterpolation::new(*transform, confirm_history.last_tick()))
            .observe(on_part_click)
            .observe(open_crafting_ui);
    }

M crates/unified/src/client/planet/incoming_planets.rs => crates/unified/src/client/planet/incoming_planets.rs +8 -34
@@ 1,12 1,11 @@
use crate::shared::config::planet::{Planet, PlanetSpring, PlanetSpringJoint, SpecialSpriteProperties};
use crate::client::interpolation::TransformInterpolation;
use crate::shared::config::planet::{Planet, SpecialSpriteProperties};
use crate::prelude::*;
use crate::shared::ecs::{MAIN_STAR_LAYERS};

const PLANET_SPRING_COMPLIANCE: f64 = 0.01;
const PLANET_SPRING_DAMPING: f64 = 0.1;
use bevy_replicon::client::confirm_history::ConfirmHistory;

pub fn incoming_planets_plugin(app: &mut App) {
    app.add_systems(Update, (handle_incoming_planets, handle_updated_planets, setup_planet_spring_joints));
    app.add_systems(Update, (handle_incoming_planets, handle_updated_planets));
}

fn build_planet_sprite(planet: &Planet, asset_server: &AssetServer) -> Sprite {


@@ 20,13 19,14 @@ fn build_planet_sprite(planet: &Planet, asset_server: &AssetServer) -> Sprite {

fn handle_incoming_planets(
    mut commands: Commands,
    new_planets: Query<(Entity, &Planet), Added<Planet>>,
    new_planets: Query<(Entity, &Planet, &Transform, &ConfirmHistory), Added<Planet>>,
    asset_server: Res<AssetServer>,
) {
    for (new_entity, new_planet) in new_planets.iter() {
    for (new_entity, new_planet, transform, confirm_history) in new_planets.iter() {
        commands.entity(new_entity)
            .insert(MAIN_STAR_LAYERS.clone())
            .insert(build_planet_sprite(new_planet, &asset_server));
            .insert(build_planet_sprite(new_planet, &asset_server))
            .insert(TransformInterpolation::new(*transform, confirm_history.last_tick()));
        trace!(?new_planet, "prepared new planet");
    }
}


@@ 43,29 43,3 @@ fn handle_updated_planets(
        trace!(?updated_planet, "updated planet");
    }
}

#[derive(Component)]
struct SpringJointReady;

fn setup_planet_spring_joints(
    unmatched_springs: Query<(Entity, &PlanetSpring), Without<SpringJointReady>>,
    planets: Query<(Entity, &Planet)>,
    mut commands: Commands,
) {
    for (spring_entity, spring) in unmatched_springs.iter() {
        let Some((planet_entity, _)) = planets.iter().find(|(_, p)| p.name == spring.name) else {
            continue;
        };
        commands.entity(spring_entity).insert((
            RigidBody::Kinematic,
            LinearVelocity::default(),
            SpringJointReady,
        ));
        commands.spawn((
            PlanetSpringJoint { name: spring.name.clone() },
            FixedJoint::new(planet_entity, spring_entity)
                .with_point_compliance(PLANET_SPRING_COMPLIANCE),
            JointDamping { linear: PLANET_SPRING_DAMPING, angular: 1.0 },
        ));
    }
}

M crates/unified/src/client/plugins.rs => crates/unified/src/client/plugins.rs +1 -0
@@ 33,6 33,7 @@ impl PluginGroup for ClientPluginGroup {
            .add(InputManagerPlugin::<crate::client::input::ClientAction>::default())
            .add(bevy_replicon::client::ClientPlugin)
            .add(bevy_replicon::client::message::ClientMessagePlugin)
            .add(bevy_replicon_renet2::client::RepliconRenetClientPlugin)
    }
}


M crates/unified/src/server/gravity.rs => crates/unified/src/server/gravity.rs +50 -1
@@ 1,7 1,56 @@
use crate::prelude::*;
use crate::server::system_sets::WorldUpdateSet;
use crate::shared::gravity::update_gravity;
use crate::shared::config::planet::Planet;
use crate::shared::ecs::Part;
use crate::shared::world_config::WorldConfigResource;

pub fn newtonian_gravity_plugin(app: &mut App) {
    app.add_systems(Update, update_gravity.in_set(WorldUpdateSet));
}

fn update_gravity(
    mut part_query: Query<(&Position, &LinearVelocity, &Mass, &mut ConstantForce), With<Part>>,
    planet_query: Query<(&Position, &Mass), With<Planet>>,
    world_config: Res<WorldConfigResource>,
    time: Res<Time>,
) {
    let Some(world_config) = &world_config.config else {
        return;
    };

    for (part_position, part_velocity, part_mass, mut forces) in &mut part_query {
        *forces = ConstantForce::new(0.0, 0.0);

        let part_mass = part_mass.0 as f64;
        let part_pos = part_position.0;

        for (planet_position, planet_mass) in &planet_query {
            let planet_mass = planet_mass.0 as f64;
            let planet_pos = planet_position.0;

            let distance = planet_pos.distance(part_pos);

            let mut x = 0.0;
            let mut total_f = 0.0;
            let mut v = part_velocity.0;
            let dt = time.delta_secs() as f64 / world_config.world.gravity_iterations as f64;
            for i in 0..world_config.world.gravity_iterations {
                let f =
                    world_config.world.gravity * ((part_mass * planet_mass) / ((distance - x)*(distance - x)));
                let dx = dt*(v.project_onto(planet_pos - part_pos)).length()
                    - 1.0/2.0*(f/part_mass)*(dt*dt);
                x += dx;
                v += f/part_mass*dt;
                if i == 0 || i == world_config.world.gravity_iterations-1 {
                    total_f += f;
                } else {
                    total_f += 2.0*f;
                }
            }
            let force = (dt/2.0*(total_f))/time.delta_secs() as f64;
            let direction = (planet_pos - part_pos).normalize() * force;
            forces.x += direction.x;
            forces.y += direction.y;
        }
    }
}

A crates/unified/src/server/management.rs => crates/unified/src/server/management.rs +32 -0
@@ 0,0 1,32 @@
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener};

use crate::shared::net::ManagementInfo;

pub fn spawn_management_server(bind: SocketAddr, info: ManagementInfo) {
    let listener = TcpListener::bind(bind).expect("failed to bind management TCP socket");
    let body = serde_json::to_string(&info).expect("failed to serialize management info");

    std::thread::spawn(move || {
        for stream in listener.incoming() {
            let Ok(mut stream) = stream else { continue };
            let body = body.clone();
            std::thread::spawn(move || {
                let mut buf = [0u8; 1024];
                let _ = stream.read(&mut buf);

                let response = format!(
                    "HTTP/1.1 200 OK\r\n\
                     Content-Type: application/json\r\n\
                     Access-Control-Allow-Origin: *\r\n\
                     Content-Length: {}\r\n\
                     Connection: close\r\n\
                     \r\n\
                     {body}",
                    body.len(),
                );
                let _ = stream.write_all(response.as_bytes());
            });
        }
    });
}

M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +72 -50
@@ 5,37 5,52 @@ mod heat;
mod drill;
mod craft;
mod damping;
mod management;
pub mod planets;
pub mod player;
mod system_sets;
mod priority;
pub mod orbit;
pub mod plugins;
pub mod components;
pub mod visibility;

use std::net::SocketAddr;
use bevy_replicon::prelude::Replicated;
use std::net::{SocketAddr, UdpSocket};
use bevy_replicon::prelude::{ConnectedClient, Replicated, RepliconChannels};
use bevy_replicon::server::AuthorizedClient;
use bevy_replicon_renet2::RenetChannelsExt;
use bevy_replicon_renet2::netcode::{
    BoxedSocket, NativeSocket, NetcodeServerTransport, ServerAuthentication, ServerSetupConfig,
    WebTransportServer, WebTransportServerConfig,
};
use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetServer};
use web_time::SystemTime;
use crate::server::craft::craft_plugin;
use crate::server::damping::damping_plugin;
use crate::server::drill::drill_plugin;
use crate::server::earth_parts::spawn_parts_plugin;
use crate::server::gravity::newtonian_gravity_plugin;
use crate::server::management::spawn_management_server;
use crate::server::part::part_management_plugin;
use crate::server::priority::replication_priority_plugin;
use crate::server::planets::planets_plugin;
use crate::server::player::player_management_plugin;
use crate::server::system_sets::{PlayerInputSet, WorldUpdateSet};
use crate::prelude::*;
use crate::server::orbit::OrbitPlugin;
use crate::server::player::thrust::server_thrust_plugin;
use crate::shared::net::{encode_cert_hash, ManagementInfo, STARKINGDOMS_PROTOCOL_MAGIC};

const MAX_CLIENTS: usize = 32;

pub struct ServerPlugin {
    pub bind: SocketAddr
    pub management_bind: SocketAddr,
    pub native_bind: SocketAddr,
    pub wt_bind: SocketAddr,
}

impl Plugin for ServerPlugin {
    fn build(&self, app: &mut App) {
        let bind = self.bind;
        app
            .add_plugins(planets_plugin)
            .add_plugins(newtonian_gravity_plugin)


@@ 50,11 65,56 @@ impl Plugin for ServerPlugin {
            .add_plugins(craft_plugin)
            .add_plugins(OrbitPlugin)
            .add_plugins(damping_plugin)
            .add_plugins(replication_priority_plugin)
            .configure_sets(Update, WorldUpdateSet.before(PlayerInputSet))
            .add_systems(Update, handle_authorized);
            .add_systems(Update, handle_authorized)
            .add_observer(on_client_disconnected);

        let management_bind = self.management_bind;
        let native_bind = self.native_bind;
        let wt_bind = self.wt_bind;
        app.add_systems(Startup, move |mut commands: Commands, channels: Res<RepliconChannels>| {
            let connection_config = ConnectionConfig::from_channels(channels.server_configs(), channels.client_configs());
            let server = RenetServer::new(connection_config);

            let native_socket = NativeSocket::new(UdpSocket::bind(native_bind).expect("failed to bind server UDP socket"))
                .expect("failed to create native socket");

            let (wt_config, cert_hash) = WebTransportServerConfig::new_selfsigned(wt_bind, MAX_CLIENTS)
                .expect("failed to create self-signed WebTransport server config");
            let runtime = tokio::runtime::Runtime::new().expect("failed to create tokio runtime");
            let wt_socket = WebTransportServer::new(wt_config, runtime.handle().clone())
                .expect("failed to create WebTransport server socket");

            let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
            let server_config = ServerSetupConfig {
                current_time,
                max_clients: MAX_CLIENTS,
                protocol_id: STARKINGDOMS_PROTOCOL_MAGIC,
                socket_addresses: vec![vec![native_bind], vec![wt_bind]],
                authentication: ServerAuthentication::Unsecure,
            };
            let transport = NetcodeServerTransport::new_with_sockets(server_config, vec![
                BoxedSocket::new(native_socket),
                BoxedSocket::new(wt_socket),
            ]).expect("failed to create server transport");

            spawn_management_server(management_bind, ManagementInfo {
                native_port: native_bind.port(),
                wt_port: wt_bind.port(),
                cert_hash: encode_cert_hash(&cert_hash.hash),
            });

            commands.insert_resource(server);
            commands.insert_resource(transport);
            commands.insert_resource(TokioRuntime(runtime));
        });
    }
}

#[derive(Resource)]
struct TokioRuntime(tokio::runtime::Runtime);

#[derive(Component, Debug)]
pub struct ConnectedGameEntity {
    pub network_entity: Entity,


@@ 64,27 124,6 @@ pub struct ConnectedNetworkEntity {
    pub game_entity: Entity,
}

/*
fn on_opened(trigger: On<Add, Server>, servers: Query<&LocalAddr>) {
    let server = trigger.event_target();
    let local_addr = servers.get(server).unwrap();
    info!(server_entity=?server, "websocket server opened on {:?}", *local_addr);
}

fn on_connected(
    trigger: On<Add, Session>,
    clients: Query<&ChildOf>,
    sessions: Query<&Session>,
    mut commands: Commands,
) {
    let client = trigger.event_target();
    let Ok(&ChildOf(server)) = clients.get(client) else {
        return;
    };
    info!(?client, ?server, "client connected");
}
*/

fn handle_authorized(
    newly_authorized_clients: Query<Entity, Added<AuthorizedClient>>,
    mut commands: Commands


@@ 104,34 143,17 @@ fn handle_authorized(
        ));
    }
}
/*
fn on_disconnected(
    trigger: On<Disconnected>,
    clients: Query<&ChildOf>,
    player_entity: Query<&ConnectedNetworkEntity>,

fn on_client_disconnected(
    trigger: On<Remove, ConnectedClient>,
    network_entities: Query<&ConnectedNetworkEntity>,
    mut commands: Commands,
) {
    let client = trigger.event_target();
    let Ok(&ChildOf(server)) = clients.get(client) else {
    let Ok(connected) = network_entities.get(client) else {
        return;
    };
    match &trigger.reason {
        DisconnectReason::ByUser(reason) => {
            info!(?client, ?server, ?reason, "client disconnected by user");
        }
        DisconnectReason::ByPeer(reason) => {
            info!(?client, ?server, ?reason, "client disconnected by peer");
        }
        DisconnectReason::ByError(err) => {
            warn!(?client, ?server, "client disconnected with error: {err:?}");
        }
    if let Ok(mut entity) = commands.get_entity(connected.game_entity) {
        entity.despawn();
    }
    let Ok(other_entity) = player_entity.get(client) else {
        return;
    };
    let Ok(mut commands) = commands.get_entity(other_entity.game_entity) else {
        return;
    };
    commands.despawn();
}
*/
\ No newline at end of file

M crates/unified/src/server/orbit/mod.rs => crates/unified/src/server/orbit/mod.rs +77 -1
@@ 1,1 1,77 @@
pub use crate::shared::orbit::OrbitPlugin;
use std::collections::HashMap;
use std::f64::consts::PI;
use avian2d::math::TAU;
use avian2d::prelude::{LinearVelocity, Mass};
use bevy::prelude::{App, Plugin, Transform, Update};
use bevy::time::Time;
use crate::shared::config::planet::{Planet, PlanetSpring};
use crate::prelude::{Query, Res, Without};
use crate::shared::ecs::TimeOffset;
use crate::shared::world_config::WorldConfigResource;

pub struct OrbitPlugin;
impl Plugin for OrbitPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Update, update_orbits);
    }
}

fn update_orbits(
    mut planets: Query<(&Planet, &Transform, &mut LinearVelocity), Without<PlanetSpring>>,
    planets_2: Query<(&Planet, &Transform, &Mass), Without<PlanetSpring>>,
    mut planet_springs: Query<(&PlanetSpring, &mut Transform, &mut LinearVelocity), Without<Planet>>,
    world_config: Res<WorldConfigResource>,
    time: Res<Time>,
    time_offset: Res<TimeOffset>,
) {
    let Some(ref world_config) = world_config.config else {
        return;
    };
    let parent_velocities = planets.iter().map(|u| (u.0.name.clone(), u.2.clone())).collect::<HashMap<String, LinearVelocity>>();
    for (planet, _, _vel) in planets.iter_mut() {
        let Some(orbit_data) = &planet.orbit else { continue; };
        let Some(parent) = planets_2.iter().find(|u| u.0.name == orbit_data.orbiting) else { continue; };

        let (parent_data, parent_transform, parent_mass) = parent;
        let a = (planet.default_position.x as f64 - parent_data.default_position.x as f64) / (1.0 - orbit_data.eccentricity);
        let e = orbit_data.eccentricity;
        let t = world_config.world.orbit_scale_factor * 2.0*PI*((a*a*a)/(world_config.world.gravity*(**parent_mass as f64))).sqrt();

        let time = time.elapsed_secs_f64() + time_offset.0;

        let m = (TAU / t) * time;
        let e_k = iterative_kepler(m, e);
        let nu = 2.0_f64 * ((1.0 + e).sqrt() * (e_k / 2.0).sin())
            .atan2((1.0 - e).sqrt() * (e_k / 2.0).cos());
        let r = a * (1.0 - e * e_k.cos());

        let x = r * nu.cos();
        let y = r * nu.sin();

        let Some(mut planet_spring) = planet_springs.iter_mut().find(|u| u.0.name == planet.name) else { continue; };
        planet_spring.1.translation.x = x as f32 + parent_transform.translation.x;
        planet_spring.1.translation.y = y as f32 + parent_transform.translation.y;

        let Some(parent_velocity) = parent_velocities.get(&orbit_data.orbiting) else { continue; };
        let de_dt = (TAU / t) / (1.0 - e * e_k.cos());
        let b_factor = (1.0 - e * e).sqrt();
        let vx = -a * e_k.sin() * de_dt;
        let vy = a * b_factor * e_k.cos() * de_dt;
        planet_spring.2.x = vx + parent_velocity.x;
        planet_spring.2.y = vy + parent_velocity.y;
    }
}

fn iterative_kepler(m: f64, e: f64) -> f64 {
    const MAX_ITERATIONS: u32 = 100;
    const CONVERGENCE_THRESHOLD: f64 = 1e-10;
    let mut output = m;
    for _ in 0..MAX_ITERATIONS {
        let d = (m - output + e * output.sin()) / (1.0 - e * output.cos());
        output += d;
        if d.abs() < CONVERGENCE_THRESHOLD {
            break;
        }
    }
    output
}

M crates/unified/src/server/planets.rs => crates/unified/src/server/planets.rs +1 -1
@@ 80,7 80,7 @@ pub fn update_planets(
                            let g = world_config.world.gravity * parent_planet.mass as f64
                                / r.length_squared();
                            // tangential velocity
                            let v = (g*r.length() as f64).sqrt();
                            let v = (1.0/world_config.world.orbit_scale_factor) * (g*r.length() as f64).sqrt();
                            let v_dir = DVec3::Z.cross(r).truncate().normalize();
                            let v = v_dir * v;
                            planet_entity.insert(LinearVelocity(v));

M crates/unified/src/server/player/join.rs => crates/unified/src/server/player/join.rs +2 -2
@@ 1,4 1,4 @@
use bevy_replicon::prelude::{ClientId, SendMode, ToClients};
use bevy_replicon::prelude::{ClientId, SendTargets, ToClients};
use crate::client::components::Me;
use crate::shared::config::planet::Planet;
use crate::shared::config::world::GlobalWorldConfig;


@@ 83,7 83,7 @@ pub fn handle_new_players(
    for joined_player in &q_new_clients {
        debug!(?joined_player, "new player!");
        welcome_messages.write(ToClients {
            mode: SendMode::Direct(ClientId::Client(joined_player.1.network_entity)),
            targets: SendTargets::Single(ClientId::Client(joined_player.1.network_entity)),
            message: Hi {
                you_are: joined_player.0,
                time_offset: time.elapsed_secs_f64(),

M crates/unified/src/server/plugins.rs => crates/unified/src/server/plugins.rs +17 -1
@@ 1,7 1,10 @@
use std::time::Duration;
use bevy::app::{PluginGroup, PluginGroupBuilder, ScheduleRunnerPlugin};
use bevy::app::{App, PluginGroup, PluginGroupBuilder, ScheduleRunnerPlugin};
use crate::prelude::*;
use crate::shared::plugins::TICK_RATE;

const PHYSICS_LENGTH_UNIT: f64 = 100.0;

pub struct ServerPluginGroup;

impl PluginGroup for ServerPluginGroup {


@@ 10,5 13,18 @@ impl PluginGroup for ServerPluginGroup {
            .add(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(1.0 / TICK_RATE)))
            .add(bevy_replicon::server::ServerPlugin::default())
            .add(bevy_replicon::server::message::ServerMessagePlugin)
            .add(bevy_replicon_renet2::server::RepliconRenetServerPlugin)
            .add_group(
                PhysicsPlugins::default()
                    .with_length_unit(PHYSICS_LENGTH_UNIT)
                    .set(PhysicsInterpolationPlugin::interpolate_all())
                    .build()
                    .disable::<IslandPlugin>()
            )
            .add(physics_setup_plugin)
    }
}

fn physics_setup_plugin(app: &mut App) {
    app.insert_resource(Gravity::ZERO);
}

A crates/unified/src/server/priority.rs => crates/unified/src/server/priority.rs +32 -0
@@ 0,0 1,32 @@
use bevy_replicon::prelude::{AuthorizedClient, PriorityMap};
use crate::prelude::*;
use crate::server::ConnectedNetworkEntity;
use crate::shared::attachment::Parts;
use crate::shared::ecs::{Part, Player};

const OTHER_SHIP_PART_PRIORITY: f32 = 0.25;

pub fn replication_priority_plugin(app: &mut App) {
    app.add_systems(Update, prioritize_own_ship_parts);
}

fn prioritize_own_ship_parts(
    mut clients: Query<(&ConnectedNetworkEntity, &mut PriorityMap), With<AuthorizedClient>>,
    hearties: Query<Option<&Parts>, With<Player>>,
    parts: Query<Entity, With<Part>>,
) {
    for (connected, mut priority) in &mut clients {
        let own_parts = hearties.get(connected.game_entity).ok().flatten();

        for part in &parts {
            let is_own = part == connected.game_entity
                || own_parts.is_some_and(|owned| owned.contains(&part));

            if is_own {
                priority.remove(&part);
            } else {
                priority.insert(part, OTHER_SHIP_PART_PRIORITY);
            }
        }
    }
}

M crates/unified/src/shared/config/world.rs => crates/unified/src/shared/config/world.rs +1 -0
@@ 15,6 15,7 @@ pub struct WorldConfig {
    pub gravity_iterations: usize,
    pub spawn_parts_at: String,
    pub spawn_parts_interval_secs: f32,
    pub orbit_scale_factor: f64
}

#[derive(Deserialize, Asset, TypePath, Clone, Debug)]

D crates/unified/src/shared/gravity.rs => crates/unified/src/shared/gravity.rs +0 -51
@@ 1,51 0,0 @@
use crate::shared::config::planet::Planet;
use crate::shared::ecs::Part;
use crate::prelude::*;
use crate::shared::world_config::WorldConfigResource;

pub fn update_gravity(
    mut part_query: Query<(&Position, &LinearVelocity, &Mass, &mut ConstantForce), With<Part>>,
    planet_query: Query<(&Position, &Mass), With<Planet>>,
    world_config: Res<WorldConfigResource>,
    time: Res<Time>,
) {
    let Some(world_config) = &world_config.config else {
        return;
    };

    for (part_position, part_velocity, part_mass, mut forces) in &mut part_query {
        *forces = ConstantForce::new(0.0, 0.0);

        let part_mass = part_mass.0 as f64;
        let part_pos = part_position.0;

        for (planet_position, planet_mass) in &planet_query {
            let planet_mass = planet_mass.0 as f64;
            let planet_pos = planet_position.0;

            let distance = planet_pos.distance(part_pos);

            let mut x = 0.0;
            let mut total_f = 0.0;
            let mut v = part_velocity.0;
            let dt = time.delta_secs() as f64 / world_config.world.gravity_iterations as f64;
            for i in 0..world_config.world.gravity_iterations {
                let f =
                    world_config.world.gravity * ((part_mass * planet_mass) / ((distance - x)*(distance - x)));
                let dx = dt*(v.project_onto(planet_pos - part_pos)).length()
                    - 1.0/2.0*(f/part_mass)*(dt*dt);
                x += dx;
                v += f/part_mass*dt;
                if i == 0 || i == world_config.world.gravity_iterations-1 {
                    total_f += f;
                } else {
                    total_f += 2.0*f;
                }
            }
            let force = (dt/2.0*(total_f))/time.delta_secs() as f64;
            let direction = (planet_pos - part_pos).normalize() * force;
            forces.x += direction.x;
            forces.y += direction.y;
        }
    }
}

M crates/unified/src/shared/mod.rs => crates/unified/src/shared/mod.rs +0 -2
@@ 1,7 1,5 @@
pub mod attachment;
pub mod config;
pub mod gravity;
pub mod orbit;
pub mod physics;
pub mod thrust;
pub mod world_config;

M crates/unified/src/shared/net.rs => crates/unified/src/shared/net.rs +44 -0
@@ 4,11 4,15 @@ use bevy::prelude::*;
use crate::prelude::{App, Message};
use bevy_replicon::prelude::*;
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use url::{Host, Url};
use crate::shared::attachment::{Joint, JointOf, PartInShip, Peer, Ship, SnapOf, SnapOfJoint};
use crate::shared::config::planet::{Planet, PlanetSpring, PlanetSpringJoint};
use crate::shared::ecs::{CanCraft, Drill, Part, Player, PlayerStorage, SingleStorage, Temperature};
use crate::shared::ecs::thruster::{Thruster, ThrusterOfPart};

pub const STARKINGDOMS_PROTOCOL_MAGIC: u64 = 0x5a5a_e37e_4aaa;

pub fn register_replication(app: &mut App) {
    app
        .add_mapped_server_message::<Hi>(Channel::Ordered)


@@ 53,3 57,43 @@ pub struct Hi {
    pub you_are: Entity,
    pub time_offset: f64,
}

#[derive(Serialize, Deserialize)]
pub struct ManagementInfo {
    pub native_port: u16,
    pub wt_port: u16,
    pub cert_hash: String,
}

/// wasm is hard
pub fn parse_management_address(s: &str) -> Result<SocketAddr, String> {
    let url = Url::parse(s).map_err(|err| format!("invalid server URL: {err}"))?;

    let ip = match url.host() {
        Some(Host::Ipv4(ip)) => IpAddr::V4(ip),
        Some(Host::Ipv6(ip)) => IpAddr::V6(ip),
        Some(Host::Domain(domain)) => domain.parse::<IpAddr>()
            .map_err(|_| format!("server host must be an IP address, got `{domain}`"))?,
        None => return Err("server URL is missing a host".to_string()),
    };
    let port = url.port().ok_or("server URL is missing a port")?;

    Ok(SocketAddr::new(ip, port))
}

pub fn encode_cert_hash(hash: &[u8; 32]) -> String {
    hash.iter().map(|byte| format!("{byte:02x}")).collect()
}

pub fn decode_cert_hash(s: &str) -> Result<[u8; 32], String> {
    if s.len() != 64 {
        return Err(format!("cert_hash must be 64 hex chars, got {}", s.len()));
    }

    let mut hash = [0u8; 32];
    for (byte, chunk) in hash.iter_mut().zip(s.as_bytes().chunks_exact(2)) {
        let chunk = std::str::from_utf8(chunk).unwrap();
        *byte = u8::from_str_radix(chunk, 16).map_err(|err| format!("invalid cert_hash: {err}"))?;
    }
    Ok(hash)
}

D crates/unified/src/shared/orbit.rs => crates/unified/src/shared/orbit.rs +0 -78
@@ 1,78 0,0 @@
use std::collections::HashMap;
use std::f64::consts::PI;
use avian2d::math::TAU;
use avian2d::prelude::{LinearVelocity, Mass};
use bevy::log::debug;
use bevy::prelude::{App, Plugin, Transform, Update};
use bevy::time::Time;
use crate::shared::config::planet::{Planet, PlanetSpring};
use crate::prelude::{Query, Res, Without};
use crate::shared::ecs::TimeOffset;
use crate::shared::world_config::WorldConfigResource;

pub struct OrbitPlugin;
impl Plugin for OrbitPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Update, update_orbits);
    }
}

pub fn update_orbits(
    mut planets: Query<(&Planet, &Transform, &mut LinearVelocity), Without<PlanetSpring>>,
    planets_2: Query<(&Planet, &Transform, &Mass), Without<PlanetSpring>>,
    mut planet_springs: Query<(&PlanetSpring, &mut Transform, &mut LinearVelocity), Without<Planet>>,
    world_config: Res<WorldConfigResource>,
    time: Res<Time>,
    time_offset: Res<TimeOffset>,
) {
    let Some(ref world_config) = world_config.config else {
        return;
    };
    let parent_velocities = planets.iter().map(|u| (u.0.name.clone(), u.2.clone())).collect::<HashMap<String, LinearVelocity>>();
    for (planet, _, _vel) in planets.iter_mut() {
        let Some(orbit_data) = &planet.orbit else { continue; };
        let Some(parent) = planets_2.iter().find(|u| u.0.name == orbit_data.orbiting) else { continue; };

        let (parent_data, parent_transform, parent_mass) = parent;
        let a = (planet.default_position.x as f64 - parent_data.default_position.x as f64) / (1.0 - orbit_data.eccentricity);
        let e = orbit_data.eccentricity;
        let t = 2.0*PI*((a*a*a)/(world_config.world.gravity*(**parent_mass as f64))).sqrt();

        let time = time.elapsed_secs_f64() + time_offset.0;

        let m = (TAU / t) * time;
        let e_k = iterative_kepler(m, e);
        let nu = 2.0_f64 * ((1.0 + e).sqrt() * (e_k / 2.0).sin())
            .atan2((1.0 - e).sqrt() * (e_k / 2.0).cos());
        let r = a * (1.0 - e * e_k.cos());

        let x = r * nu.cos();
        let y = r * nu.sin();

        let Some(mut planet_spring) = planet_springs.iter_mut().find(|u| u.0.name == planet.name) else { continue; };
        planet_spring.1.translation.x = x as f32 + parent_transform.translation.x;
        planet_spring.1.translation.y = y as f32 + parent_transform.translation.y;

        let Some(parent_velocity) = parent_velocities.get(&orbit_data.orbiting) else { continue; };
        let de_dt = (TAU / t) / (1.0 - e * e_k.cos());
        let b_factor = (1.0 - e * e).sqrt();
        let vx = -a * e_k.sin() * de_dt;
        let vy = a * b_factor * e_k.cos() * de_dt;
        planet_spring.2.x = vx + parent_velocity.x;
        planet_spring.2.y = vy + parent_velocity.y;
    }
}

fn iterative_kepler(m: f64, e: f64) -> f64 {
    const MAX_ITERATIONS: u32 = 100;
    const CONVERGENCE_THRESHOLD: f64 = 1e-10;
    let mut output = m;
    for _ in 0..MAX_ITERATIONS {
        let d = (m - output + e * output.sin()) / (1.0 - e * output.cos());
        output += d;
        if d.abs() < CONVERGENCE_THRESHOLD {
            break;
        }
    }
    output
}

M crates/unified/src/shared/plugins.rs => crates/unified/src/shared/plugins.rs +0 -16
@@ 14,7 14,6 @@ use crate::shared::net::register_replication;
use crate::shared::world_config::world_config_plugin;

pub const TICK_RATE: f64 = 20.0;
const PHYSICS_LENGTH_UNIT: f64 = 100.0;

pub struct SharedPluginGroup;



@@ 30,14 29,6 @@ impl PluginGroup for SharedPluginGroup {
                app.insert_resource(Time::from_hz(TICK_RATE));
                app.insert_resource(TimeOffset::default());
            })
            .add_group(
                PhysicsPlugins::default()
                    .with_length_unit(PHYSICS_LENGTH_UNIT)
                    .set(PhysicsInterpolationPlugin::interpolate_all())
                    .build()
                    .disable::<IslandPlugin>()
            )
            .add(physics_setup_plugin)
            .add(register_replication)
            .add(register_everything)
            .add(world_config_plugin)


@@ 56,10 47,3 @@ pub fn register_everything(app: &mut App) {
    app.add_message::<CraftPartRequest>();
    app.add_message::<ThrustSolution>();
}

fn physics_setup_plugin(app: &mut App) {
    app.insert_resource(Gravity::ZERO);
    app.add_systems(Startup, setup_physics);
}

fn setup_physics() {}

M crates/unified/src/universal_entrypoint.rs => crates/unified/src/universal_entrypoint.rs +2 -2
@@ 24,9 24,9 @@ pub fn run(cli: StkArgs) -> AppExit {
            app.add_plugins(ClientPlugin { server: Some(server) });
        },
        #[cfg(not(target_arch = "wasm32"))]
        StkArgs::Server { bind_to, with_client } => {
        StkArgs::Server { management_bind, native_bind, wt_bind, with_client } => {
            app.add_plugins(ServerPluginGroup);
            app.add_plugins(ServerPlugin { bind: bind_to });
            app.add_plugins(ServerPlugin { management_bind, native_bind, wt_bind });
            if with_client {
                app.add_plugins(ClientPluginGroup);
                app.add_plugins(ClientPlugin { server: None });