commit fd02ed6d8b54b6de1b6c0cd76489a89536c888f9 Author: irrlicht Date: Wed Jun 17 08:23:13 2026 +0200 Resumed to project. INIT diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c838446 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Rust build artifacts +/target + +# Editor / OS cruft +*.swp +*~ +.DS_Store diff --git a/Cargo.lock b/Cargo.lock new file mode 100755 index 0000000..2e6ca35 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2419 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.10.0", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.17", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.10.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "client" +version = "0.1.0" +dependencies = [ + "bytemuck", + "pbio", + "shared", +] + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.10.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.10.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.17", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "bitflags 2.10.0", + "libc", + "plain", + "redox_syscall 0.8.1", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "metal" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" +dependencies = [ + "bitflags 2.10.0", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "naga" +version = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.0", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash", + "spirv", + "thiserror 2.0.17", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.10.0", + "jni-sys 0.3.0", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.0", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.10.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.10.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbio" +version = "0.1.0" +source = "git+https://codeberg.org/irrlicht/rust-pbio.git#2d814718200a5585216f90766693bca1582b3c0e" +dependencies = [ + "bytemuck", + "pollster", + "wgpu", + "winit", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "server" +version = "0.1.0" +dependencies = [ + "bytemuck", + "shared", +] + +[[package]] +name = "shared" +version = "0.1.0" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.10.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db3bae107c9522f86d361697dee1d7386a2ddcf659d5aea5159819a21a3c4a7" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.10.0", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.10.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +dependencies = [ + "arrayvec", + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.0", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.10.0", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.0", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 2.0.17", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "27.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.10.0", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types 0.2.0", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.0", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.17", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows", + "windows-core", +] + +[[package]] +name = "wgpu-types" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.17", + "web-sys", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.10.0", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.10.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100755 index 0000000..7c78d6d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["shared", "client", "server"] +resolver = "2" diff --git a/Units.tga b/Units.tga new file mode 100755 index 0000000..7e32096 Binary files /dev/null and b/Units.tga differ diff --git a/assets/.rgb332.gpl.kak.oHYRBE b/assets/.rgb332.gpl.kak.oHYRBE new file mode 100644 index 0000000..baa2200 --- /dev/null +++ b/assets/.rgb332.gpl.kak.oHYRBE @@ -0,0 +1,260 @@ +GIMP Palette +Name: RGB332 +Columns: 16 +# + 0 0 0 Index 0 +255 0 0 Index 1 + 0 0 170 Index 2 + 0 0 255 Index 3 + 0 36 0 Index 4 + 0 36 85 Index 5 + 0 36 170 Index 6 + 0 36 255 Index 7 + 0 72 0 Index 8 + 0 72 85 Index 9 + 0 72 170 Index 10 + 0 72 255 Index 11 + 0 109 0 Index 12 + 0 109 85 Index 13 + 0 109 170 Index 14 + 0 109 255 Index 15 + 0 145 0 Index 16 + 0 145 85 Index 17 + 0 145 170 Index 18 + 0 145 255 Index 19 + 0 182 0 Index 20 + 0 182 85 Index 21 + 0 182 170 Index 22 + 0 182 255 Index 23 + 0 218 0 Index 24 + 0 218 85 Index 25 + 0 218 170 Index 26 + 0 218 255 Index 27 + 0 255 0 Index 28 + 0 255 85 Index 29 + 0 255 170 Index 30 + 0 255 255 Index 31 + 36 0 0 Index 32 + 36 0 85 Index 33 + 36 0 170 Index 34 + 36 0 255 Index 35 + 36 36 0 Index 36 + 36 36 85 Index 37 + 36 36 170 Index 38 + 36 36 255 Index 39 + 36 72 0 Index 40 + 36 72 85 Index 41 + 36 72 170 Index 42 + 36 72 255 Index 43 + 36 109 0 Index 44 + 36 109 85 Index 45 + 36 109 170 Index 46 + 36 109 255 Index 47 + 36 145 0 Index 48 + 36 145 85 Index 49 + 36 145 170 Index 50 + 36 145 255 Index 51 + 36 182 0 Index 52 + 36 182 85 Index 53 + 36 182 170 Index 54 + 36 182 255 Index 55 + 36 218 0 Index 56 + 36 218 85 Index 57 + 36 218 170 Index 58 + 36 218 255 Index 59 + 36 255 0 Index 60 + 36 255 85 Index 61 + 36 255 170 Index 62 + 36 255 255 Index 63 + 72 0 0 Index 64 + 72 0 85 Index 65 + 72 0 170 Index 66 + 72 0 255 Index 67 + 72 36 0 Index 68 + 72 36 85 Index 69 + 72 36 170 Index 70 + 72 36 255 Index 71 + 72 72 0 Index 72 + 72 72 85 Index 73 + 72 72 170 Index 74 + 72 72 255 Index 75 + 72 109 0 Index 76 + 72 109 85 Index 77 + 72 109 170 Index 78 + 72 109 255 Index 79 + 72 145 0 Index 80 + 72 145 85 Index 81 + 72 145 170 Index 82 + 72 145 255 Index 83 + 72 182 0 Index 84 + 72 182 85 Index 85 + 72 182 170 Index 86 + 72 182 255 Index 87 + 72 218 0 Index 88 + 72 218 85 Index 89 + 72 218 170 Index 90 + 72 218 255 Index 91 + 72 255 0 Index 92 + 72 255 85 Index 93 + 72 255 170 Index 94 + 72 255 255 Index 95 +109 0 0 Index 96 +109 0 85 Index 97 +109 0 170 Index 98 +109 0 255 Index 99 +109 36 0 Index 100 +109 36 85 Index 101 +109 36 170 Index 102 +109 36 255 Index 103 +109 72 0 Index 104 +109 72 85 Index 105 +109 72 170 Index 106 +109 72 255 Index 107 +109 109 0 Index 108 +109 109 85 Index 109 +109 109 170 Index 110 +109 109 255 Index 111 +109 145 0 Index 112 +109 145 85 Index 113 +109 145 170 Index 114 +109 145 255 Index 115 +109 182 0 Index 116 +109 182 85 Index 117 +109 182 170 Index 118 +109 182 255 Index 119 +109 218 0 Index 120 +109 218 85 Index 121 +109 218 170 Index 122 +109 218 255 Index 123 +109 255 0 Index 124 +109 255 85 Index 125 +109 255 170 Index 126 +109 255 255 Index 127 +145 0 0 Index 128 +145 0 85 Index 129 +145 0 170 Index 130 +145 0 255 Index 131 +145 36 0 Index 132 +145 36 85 Index 133 +145 36 170 Index 134 +145 36 255 Index 135 +145 72 0 Index 136 +145 72 85 Index 137 +145 72 170 Index 138 +145 72 255 Index 139 +145 109 0 Index 140 +145 109 85 Index 141 +145 109 170 Index 142 +145 109 255 Index 143 +145 145 0 Index 144 +145 145 85 Index 145 +145 145 170 Index 146 +145 145 255 Index 147 +145 182 0 Index 148 +145 182 85 Index 149 +145 182 170 Index 150 +145 182 255 Index 151 +145 218 0 Index 152 +145 218 85 Index 153 +145 218 170 Index 154 +145 218 255 Index 155 +145 255 0 Index 156 +145 255 85 Index 157 +145 255 170 Index 158 +145 255 255 Index 159 +182 0 0 Index 160 +182 0 85 Index 161 +182 0 170 Index 162 +182 0 255 Index 163 +182 36 0 Index 164 +182 36 85 Index 165 +182 36 170 Index 166 +182 36 255 Index 167 +182 72 0 Index 168 +182 72 85 Index 169 +182 72 170 Index 170 +182 72 255 Index 171 +182 109 0 Index 172 +182 109 85 Index 173 +182 109 170 Index 174 +182 109 255 Index 175 +182 145 0 Index 176 +182 145 85 Index 177 +182 145 170 Index 178 +182 145 255 Index 179 +182 182 0 Index 180 +182 182 85 Index 181 +182 182 170 Index 182 +182 182 255 Index 183 +182 218 0 Index 184 +182 218 85 Index 185 +182 218 170 Index 186 +182 218 255 Index 187 +182 255 0 Index 188 +182 255 85 Index 189 +182 255 170 Index 190 +182 255 255 Index 191 +218 0 0 Index 192 +218 0 85 Index 193 +218 0 170 Index 194 +218 0 255 Index 195 +218 36 0 Index 196 +218 36 85 Index 197 +218 36 170 Index 198 +218 36 255 Index 199 +218 72 0 Index 200 +218 72 85 Index 201 +218 72 170 Index 202 +218 72 255 Index 203 +218 109 0 Index 204 +218 109 85 Index 205 +218 109 170 Index 206 +218 109 255 Index 207 +218 145 0 Index 208 +218 145 85 Index 209 +218 145 170 Index 210 +218 145 255 Index 211 +218 182 0 Index 212 +218 182 85 Index 213 +218 182 170 Index 214 +218 182 255 Index 215 +218 218 0 Index 216 +218 218 85 Index 217 +218 218 170 Index 218 +218 218 255 Index 219 +218 255 0 Index 220 +218 255 85 Index 221 +218 255 170 Index 222 +218 255 255 Index 223 +255 0 0 Index 224 +255 0 85 Index 225 +255 0 170 Index 226 +255 0 255 Index 227 +255 36 0 Index 228 +255 36 85 Index 229 +255 36 170 Index 230 +255 36 255 Index 231 +255 72 0 Index 232 +255 72 85 Index 233 +255 72 170 Index 234 +255 72 255 Index 235 +255 109 0 Index 236 +255 109 85 Index 237 +255 109 170 Index 238 +255 109 255 Index 239 +255 145 0 Index 240 +255 145 85 Index 241 +255 145 170 Index 242 +255 145 255 Index 243 +255 182 0 Index 244 +255 182 85 Index 245 +255 182 170 Index 246 +255 182 255 Index 247 +255 218 0 Index 248 +255 218 85 Index 249 +255 218 170 Index 250 +255 218 255 Index 251 +255 255 0 Index 252 +255 255 85 Index 253 +255 255 170 Index 254 +255 255 255 Index 255 diff --git a/assets/Mini-Medieval-8x8/Animals.png b/assets/Mini-Medieval-8x8/Animals.png new file mode 100755 index 0000000..412a771 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Animals.png differ diff --git a/assets/Mini-Medieval-8x8/Crops.png b/assets/Mini-Medieval-8x8/Crops.png new file mode 100755 index 0000000..3ddd270 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Crops.png differ diff --git a/assets/Mini-Medieval-8x8/Interface.png b/assets/Mini-Medieval-8x8/Interface.png new file mode 100755 index 0000000..d0586ff Binary files /dev/null and b/assets/Mini-Medieval-8x8/Interface.png differ diff --git a/assets/Mini-Medieval-8x8/Items.png b/assets/Mini-Medieval-8x8/Items.png new file mode 100755 index 0000000..ede1568 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Items.png differ diff --git a/assets/Mini-Medieval-8x8/Misc.png b/assets/Mini-Medieval-8x8/Misc.png new file mode 100755 index 0000000..58d1d17 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Misc.png differ diff --git a/assets/Mini-Medieval-8x8/Ores.png b/assets/Mini-Medieval-8x8/Ores.png new file mode 100755 index 0000000..d247856 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Ores.png differ diff --git a/assets/Mini-Medieval-8x8/Overworld.png b/assets/Mini-Medieval-8x8/Overworld.png new file mode 100755 index 0000000..7ef34d7 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Overworld.png differ diff --git a/assets/Mini-Medieval-8x8/README.txt b/assets/Mini-Medieval-8x8/README.txt new file mode 100755 index 0000000..d33bcd3 --- /dev/null +++ b/assets/Mini-Medieval-8x8/README.txt @@ -0,0 +1,17 @@ +Mini Medieval v2.3.1 +-------------------- + + +Enjoy my assets? Keep up to date by following me on itch or x. + +https://v3x3d.itch.io/ +https://x.com/_V3X3D + +You can also support me on Patreon for just a $1 each month. +Your support helps me keep making assets and provides you with rewards. + +https://www.patreon.com/V3X3D + + +Cheers, +-- VEXED diff --git a/assets/Mini-Medieval-8x8/Ships.png b/assets/Mini-Medieval-8x8/Ships.png new file mode 100755 index 0000000..d6e1b4b Binary files /dev/null and b/assets/Mini-Medieval-8x8/Ships.png differ diff --git a/assets/Mini-Medieval-8x8/Structures.png b/assets/Mini-Medieval-8x8/Structures.png new file mode 100755 index 0000000..7b36d21 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Structures.png differ diff --git a/assets/Mini-Medieval-8x8/Units.png b/assets/Mini-Medieval-8x8/Units.png new file mode 100755 index 0000000..81f01d0 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Units.png differ diff --git a/assets/Mini-Medieval-8x8/Walls.png b/assets/Mini-Medieval-8x8/Walls.png new file mode 100755 index 0000000..95a6b29 Binary files /dev/null and b/assets/Mini-Medieval-8x8/Walls.png differ diff --git a/assets/Mini-Medieval-v2.3.1.zip b/assets/Mini-Medieval-v2.3.1.zip new file mode 100755 index 0000000..861c4f9 Binary files /dev/null and b/assets/Mini-Medieval-v2.3.1.zip differ diff --git a/assets/generate_gimp_palette.py b/assets/generate_gimp_palette.py new file mode 100755 index 0000000..d560b17 --- /dev/null +++ b/assets/generate_gimp_palette.py @@ -0,0 +1,7 @@ +with open("rgb332.gpl", "w") as f: + f.write("GIMP Palette\nName: RGB332\nColumns: 16\n#\n") + for i in range(256): + r = ((i >> 5) * 255) // 7 + g = (((i >> 2) & 0x7) * 255) // 7 + b = ((i & 0x3) * 255) // 3 + f.write(f"{r:3d} {g:3d} {b:3d} Index {i}\n") diff --git a/assets/rgb332.gpl b/assets/rgb332.gpl new file mode 100755 index 0000000..ee694b7 --- /dev/null +++ b/assets/rgb332.gpl @@ -0,0 +1,260 @@ +GIMP Palette +Name: RGB332 +Columns: 16 +# + 0 0 0 Index 0 + 0 0 0 Index 1 + 0 0 170 Index 2 + 0 0 255 Index 3 + 0 36 0 Index 4 + 0 36 85 Index 5 + 0 36 170 Index 6 + 0 36 255 Index 7 + 0 72 0 Index 8 + 0 72 85 Index 9 + 0 72 170 Index 10 + 0 72 255 Index 11 + 0 109 0 Index 12 + 0 109 85 Index 13 + 0 109 170 Index 14 + 0 109 255 Index 15 + 0 145 0 Index 16 + 0 145 85 Index 17 + 0 145 170 Index 18 + 0 145 255 Index 19 + 0 182 0 Index 20 + 0 182 85 Index 21 + 0 182 170 Index 22 + 0 182 255 Index 23 + 0 218 0 Index 24 + 0 218 85 Index 25 + 0 218 170 Index 26 + 0 218 255 Index 27 + 0 255 0 Index 28 + 0 255 85 Index 29 + 0 255 170 Index 30 + 0 255 255 Index 31 + 36 0 0 Index 32 + 36 0 85 Index 33 + 36 0 170 Index 34 + 36 0 255 Index 35 + 36 36 0 Index 36 + 36 36 85 Index 37 + 36 36 170 Index 38 + 36 36 255 Index 39 + 36 72 0 Index 40 + 36 72 85 Index 41 + 36 72 170 Index 42 + 36 72 255 Index 43 + 36 109 0 Index 44 + 36 109 85 Index 45 + 36 109 170 Index 46 + 36 109 255 Index 47 + 36 145 0 Index 48 + 36 145 85 Index 49 + 36 145 170 Index 50 + 36 145 255 Index 51 + 36 182 0 Index 52 + 36 182 85 Index 53 + 36 182 170 Index 54 + 36 182 255 Index 55 + 36 218 0 Index 56 + 36 218 85 Index 57 + 36 218 170 Index 58 + 36 218 255 Index 59 + 36 255 0 Index 60 + 36 255 85 Index 61 + 36 255 170 Index 62 + 36 255 255 Index 63 + 72 0 0 Index 64 + 72 0 85 Index 65 + 72 0 170 Index 66 + 72 0 255 Index 67 + 72 36 0 Index 68 + 72 36 85 Index 69 + 72 36 170 Index 70 + 72 36 255 Index 71 + 72 72 0 Index 72 + 72 72 85 Index 73 + 72 72 170 Index 74 + 72 72 255 Index 75 + 72 109 0 Index 76 + 72 109 85 Index 77 + 72 109 170 Index 78 + 72 109 255 Index 79 + 72 145 0 Index 80 + 72 145 85 Index 81 + 72 145 170 Index 82 + 72 145 255 Index 83 + 72 182 0 Index 84 + 72 182 85 Index 85 + 72 182 170 Index 86 + 72 182 255 Index 87 + 72 218 0 Index 88 + 72 218 85 Index 89 + 72 218 170 Index 90 + 72 218 255 Index 91 + 72 255 0 Index 92 + 72 255 85 Index 93 + 72 255 170 Index 94 + 72 255 255 Index 95 +109 0 0 Index 96 +109 0 85 Index 97 +109 0 170 Index 98 +109 0 255 Index 99 +109 36 0 Index 100 +109 36 85 Index 101 +109 36 170 Index 102 +109 36 255 Index 103 +109 72 0 Index 104 +109 72 85 Index 105 +109 72 170 Index 106 +109 72 255 Index 107 +109 109 0 Index 108 +109 109 85 Index 109 +109 109 170 Index 110 +109 109 255 Index 111 +109 145 0 Index 112 +109 145 85 Index 113 +109 145 170 Index 114 +109 145 255 Index 115 +109 182 0 Index 116 +109 182 85 Index 117 +109 182 170 Index 118 +109 182 255 Index 119 +109 218 0 Index 120 +109 218 85 Index 121 +109 218 170 Index 122 +109 218 255 Index 123 +109 255 0 Index 124 +109 255 85 Index 125 +109 255 170 Index 126 +109 255 255 Index 127 +145 0 0 Index 128 +145 0 85 Index 129 +145 0 170 Index 130 +145 0 255 Index 131 +145 36 0 Index 132 +145 36 85 Index 133 +145 36 170 Index 134 +145 36 255 Index 135 +145 72 0 Index 136 +145 72 85 Index 137 +145 72 170 Index 138 +145 72 255 Index 139 +145 109 0 Index 140 +145 109 85 Index 141 +145 109 170 Index 142 +145 109 255 Index 143 +145 145 0 Index 144 +145 145 85 Index 145 +145 145 170 Index 146 +145 145 255 Index 147 +145 182 0 Index 148 +145 182 85 Index 149 +145 182 170 Index 150 +145 182 255 Index 151 +145 218 0 Index 152 +145 218 85 Index 153 +145 218 170 Index 154 +145 218 255 Index 155 +145 255 0 Index 156 +145 255 85 Index 157 +145 255 170 Index 158 +145 255 255 Index 159 +182 0 0 Index 160 +182 0 85 Index 161 +182 0 170 Index 162 +182 0 255 Index 163 +182 36 0 Index 164 +182 36 85 Index 165 +182 36 170 Index 166 +182 36 255 Index 167 +182 72 0 Index 168 +182 72 85 Index 169 +182 72 170 Index 170 +182 72 255 Index 171 +182 109 0 Index 172 +182 109 85 Index 173 +182 109 170 Index 174 +182 109 255 Index 175 +182 145 0 Index 176 +182 145 85 Index 177 +182 145 170 Index 178 +182 145 255 Index 179 +182 182 0 Index 180 +182 182 85 Index 181 +182 182 170 Index 182 +182 182 255 Index 183 +182 218 0 Index 184 +182 218 85 Index 185 +182 218 170 Index 186 +182 218 255 Index 187 +182 255 0 Index 188 +182 255 85 Index 189 +182 255 170 Index 190 +182 255 255 Index 191 +218 0 0 Index 192 +218 0 85 Index 193 +218 0 170 Index 194 +218 0 255 Index 195 +218 36 0 Index 196 +218 36 85 Index 197 +218 36 170 Index 198 +218 36 255 Index 199 +218 72 0 Index 200 +218 72 85 Index 201 +218 72 170 Index 202 +218 72 255 Index 203 +218 109 0 Index 204 +218 109 85 Index 205 +218 109 170 Index 206 +218 109 255 Index 207 +218 145 0 Index 208 +218 145 85 Index 209 +218 145 170 Index 210 +218 145 255 Index 211 +218 182 0 Index 212 +218 182 85 Index 213 +218 182 170 Index 214 +218 182 255 Index 215 +218 218 0 Index 216 +218 218 85 Index 217 +218 218 170 Index 218 +218 218 255 Index 219 +218 255 0 Index 220 +218 255 85 Index 221 +218 255 170 Index 222 +218 255 255 Index 223 +255 0 0 Index 224 +255 0 85 Index 225 +255 0 170 Index 226 +255 0 255 Index 227 +255 36 0 Index 228 +255 36 85 Index 229 +255 36 170 Index 230 +255 36 255 Index 231 +255 72 0 Index 232 +255 72 85 Index 233 +255 72 170 Index 234 +255 72 255 Index 235 +255 109 0 Index 236 +255 109 85 Index 237 +255 109 170 Index 238 +255 109 255 Index 239 +255 145 0 Index 240 +255 145 85 Index 241 +255 145 170 Index 242 +255 145 255 Index 243 +255 182 0 Index 244 +255 182 85 Index 245 +255 182 170 Index 246 +255 182 255 Index 247 +255 218 0 Index 248 +255 218 85 Index 249 +255 218 170 Index 250 +255 218 255 Index 251 +255 255 0 Index 252 +255 255 85 Index 253 +255 255 170 Index 254 +255 255 255 Index 255 diff --git a/assets/tiles.xcf b/assets/tiles.xcf new file mode 100755 index 0000000..4aa0a8f Binary files /dev/null and b/assets/tiles.xcf differ diff --git a/assets/tilesets/entities.tga b/assets/tilesets/entities.tga new file mode 100755 index 0000000..0e06725 Binary files /dev/null and b/assets/tilesets/entities.tga differ diff --git a/assets/tilesets/entities.xcf b/assets/tilesets/entities.xcf new file mode 100755 index 0000000..3bcd6d8 Binary files /dev/null and b/assets/tilesets/entities.xcf differ diff --git a/assets/tilesets/overworld.tga b/assets/tilesets/overworld.tga new file mode 100755 index 0000000..5158ce4 Binary files /dev/null and b/assets/tilesets/overworld.tga differ diff --git a/assets/tilesets/overworld.xcf b/assets/tilesets/overworld.xcf new file mode 100755 index 0000000..0f251c7 Binary files /dev/null and b/assets/tilesets/overworld.xcf differ diff --git a/assets/ui_sketch.tga b/assets/ui_sketch.tga new file mode 100755 index 0000000..37b26c3 Binary files /dev/null and b/assets/ui_sketch.tga differ diff --git a/assets/ui_sketch_grayscale.tga b/assets/ui_sketch_grayscale.tga new file mode 100755 index 0000000..4e57ed9 Binary files /dev/null and b/assets/ui_sketch_grayscale.tga differ diff --git a/assets/ui_sketch_rle.tga b/assets/ui_sketch_rle.tga new file mode 100755 index 0000000..6abe98d Binary files /dev/null and b/assets/ui_sketch_rle.tga differ diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100755 index 0000000..333a115 --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2024" + +[dependencies] +shared = { path = "../shared" } +pbio = { git = "https://codeberg.org/irrlicht/rust-pbio.git" } +bytemuck = { version = "1.24.0", features = ["derive"] } diff --git a/client/src/assets.rs b/client/src/assets.rs new file mode 100755 index 0000000..a4facea --- /dev/null +++ b/client/src/assets.rs @@ -0,0 +1,72 @@ +use std::fs::File; +use std::io::Read; + +pub struct Image { + pub width: u32, + pub height: u32, + pub pixels: Vec, +} + +impl Image { + /// Load an 8-bit indexed TGA (type 1 or type 9) from disk. + pub fn from_tga(path: &str) -> Self { + let mut file = File::open(path).unwrap(); + let mut data = Vec::new(); + file.read_to_end(&mut data).unwrap(); + + let image_type = data[2]; + let width = u16::from_le_bytes([data[12], data[13]]) as u32; + let height = u16::from_le_bytes([data[14], data[15]]) as u32; + let colormap_entries = u16::from_le_bytes([data[5], data[6]]) as usize; + let colormap_entry_bytes = data[7] as usize / 8; + let pixel_offset = 18 + colormap_entries * colormap_entry_bytes; + + let pixel_count = (width * height) as usize; + let mut pixels = vec![0u8; pixel_count]; + match image_type { + 1 => pixels.copy_from_slice(&data[pixel_offset..pixel_offset + pixel_count]), + 9 => decode_rle(&data[pixel_offset..], &mut pixels), + _ => panic!("unsupported TGA image type: {}", image_type), + } + + Image { width, height, pixels } + } + + /// Split this image into 8×8 tiles, row-major. Tile ID = flat index into the Vec. + pub fn to_tileset(&self) -> Vec<[u8; 64]> { + let w = self.width as usize; + let cols = w / 8; + let rows = self.height as usize / 8; + let mut tiles = Vec::with_capacity(rows * cols); + for row in 0..rows { + for col in 0..cols { + let mut tile = [0u8; 64]; + for tr in 0..8 { + for tc in 0..8 { + tile[tr * 8 + tc] = self.pixels[w * (row * 8 + tr) + col * 8 + tc]; + } + } + tiles.push(tile); + } + } + tiles + } +} + +fn decode_rle(src: &[u8], dst: &mut [u8]) { + let mut si = 0; + let mut di = 0; + while di < dst.len() { + let header = src[si]; + si += 1; + let count = (header & 0x7f) as usize + 1; + if header & 0x80 != 0 { + dst[di..di + count].fill(src[si]); + si += 1; + } else { + dst[di..di + count].copy_from_slice(&src[si..si + count]); + si += count; + } + di += count; + } +} diff --git a/client/src/game.rs b/client/src/game.rs new file mode 100755 index 0000000..cfe2b4f --- /dev/null +++ b/client/src/game.rs @@ -0,0 +1,124 @@ +mod pixelhelper; + +use crate::assets::Image; +use crate::input::{GameAction, InputState}; +use crate::net::{EntityInfo, NetClient, NetEvent}; +use shared::{chunk_id, player_action}; + +pub enum GameSignal { + Quit, +} + +pub struct Game { + #[allow(dead_code)] + tileset: Vec<[u8; 64]>, + entity_tileset: Vec<[u8; 64]>, + net: NetClient, + player_entity_id: u32, + player_pos: (i32, i32), + entities: Vec, +} + +impl Game { + pub fn start() -> Self { + let image = Image::from_tga("assets/tilesets/overworld.tga"); + let tileset = image.to_tileset(); + let entity_tileset = Image::from_tga("assets/tilesets/entities.tga").to_tileset(); + + let server_addr = "127.0.0.1:7777".parse().unwrap(); + Game { + tileset, + entity_tileset, + net: NetClient::new(server_addr), + player_entity_id: 0, + player_pos: (16, 16), + entities: Vec::new(), + } + } + + pub fn update(&mut self, render_frame: &mut [u8], _dt: usize, input: &InputState) + -> Option + { + if input.button_held(GameAction::Up) { self.net.send_action(player_action::NORTH); } + if input.button_held(GameAction::Down) { self.net.send_action(player_action::SOUTH); } + if input.button_held(GameAction::Left) { self.net.send_action(player_action::WEST); } + if input.button_held(GameAction::Right) { self.net.send_action(player_action::EAST); } + + if input.button_pressed(GameAction::Confirm) { + self.net.send_ping(); + println!("ping sent"); + } + + for event in self.net.poll() { + match event { + NetEvent::Pong { rtt_ms } => println!("pong! rtt = {rtt_ms} ms"), + NetEvent::State { tick, player_entity_id } => { + println!("state tick={tick} player_entity_id={player_entity_id}"); + self.player_entity_id = player_entity_id; + } + NetEvent::Chunk { chunk_id } => println!("chunk cached id={chunk_id}"), + NetEvent::Entity { entities } => { + self.player_pos = entities.iter() + .find(|e| e.id == self.player_entity_id) + .map(|e| (e.pos_x as i32, e.pos_y as i32)) + .unwrap_or(self.player_pos); + self.entities = entities; + } + NetEvent::Disconnected => println!("disconnected from server"), + } + } + + if input.button_pressed(GameAction::Cancel) { + println!("Goodbye!"); + return Some(GameSignal::Quit); + } + + self.render_viewport(render_frame); + None + } + + fn render_viewport(&self, frame: &mut [u8]) { + const WALL: u8 = 0x00; + const W: usize = 320; + + frame.fill(0); + + let ox = self.player_pos.0 - 15; + let oy = self.player_pos.1 - 15; + + for vy in 0..30i32 { + for vx in 0..30i32 { + let wx = ox + vx; + let wy = oy + vy; + let cid = chunk_id(wx.div_euclid(32) as i16, wy.div_euclid(32) as i16); + let tile_idx = wy.rem_euclid(32) as usize * 32 + wx.rem_euclid(32) as usize; + let tile_id = self.net.chunk_cache.get(&cid) + .map(|c| c.tiles[tile_idx]) + .unwrap_or(0); + let px = vx * 8; + let py = vy * 8; + if let Some(tile) = self.tileset.get(tile_id as usize) { + pixelhelper::blit_tile(frame, W, px, py, tile); + } else { + for dy in 0..8 { + for dx in 0..8 { + pixelhelper::set_pixel(frame, W, px + dx, py + dy, WALL); + } + } + } + } + } + + // Entity pass + let ox = self.player_pos.0 - 15; + let oy = self.player_pos.1 - 15; + for e in &self.entities { + let vx = e.pos_x as i32 - ox; + let vy = e.pos_y as i32 - oy; + if vx < 0 || vx >= 30 || vy < 0 || vy >= 30 { continue; } + if let Some(tile) = self.entity_tileset.get(e.type_id as usize) { + pixelhelper::blit_tile(frame, W, vx * 8, vy * 8, tile); + } + } + } +} diff --git a/client/src/game/pixelhelper.rs b/client/src/game/pixelhelper.rs new file mode 100755 index 0000000..9ed81c8 --- /dev/null +++ b/client/src/game/pixelhelper.rs @@ -0,0 +1,71 @@ +/// Write a single palette index. Out-of-bounds coordinates are silently ignored. +#[allow(dead_code)] +pub fn set_pixel(dst: &mut [u8], dst_w: usize, x: i32, y: i32, color: u8) { + let dst_h = (dst.len() / dst_w) as i32; + if x < 0 || x >= dst_w as i32 || y < 0 || y >= dst_h { + return; + } + dst[dst_w * y as usize + x as usize] = color; +} + +/// Copy a rectangle out of `src` into a new Vec (for building sprites/glyphs). +#[allow(dead_code)] +pub fn extract(src: &[u8], src_w: usize, x: usize, y: usize, w: usize, h: usize) -> Vec { + let mut out = vec![0u8; w * h]; + for row in 0..h { + let s = src_w * (y + row) + x; + let d = w * row; + out[d..d + w].copy_from_slice(&src[s..s + w]); + } + out +} + +/// Blit `src` (width `src_w`, height derived from src.len()) into `dst` at (dx, dy). +/// Clips against all four edges of `dst`; the destination position may be negative. +#[allow(dead_code)] +pub fn blit(dst: &mut [u8], dst_w: usize, dx: i32, dy: i32, src: &[u8], src_w: usize) { + let dst_w_i = dst_w as i32; + let dst_h = (dst.len() / dst_w) as i32; + let src_h = (src.len() / src_w) as i32; + + for row in 0..src_h { + let y = dy + row; + if y < 0 || y >= dst_h { + continue; + } + // Clip the horizontal span once per row, then copy it in one shot. + let x0 = dx.max(0); + let x1 = (dx + src_w as i32).min(dst_w_i); + if x0 >= x1 { + continue; + } + let d = dst_w * y as usize + x0 as usize; + let s = (row * src_w as i32 + (x0 - dx)) as usize; + let len = (x1 - x0) as usize; + dst[d..d + len].copy_from_slice(&src[s..s + len]); + } +} + +/// Blit an 8×8 tile with transparency: index 0 is skipped. +/// Clips against all four edges of `dst`; the destination position may be negative. +pub fn blit_tile(dst: &mut [u8], dst_w: usize, dx: i32, dy: i32, tile: &[u8; 64]) { + let dst_w_i = dst_w as i32; + let dst_h = (dst.len() / dst_w) as i32; + + for row in 0..8i32 { + let y = dy + row; + if y < 0 || y >= dst_h { + continue; + } + for col in 0..8i32 { + let x = dx + col; + if x < 0 || x >= dst_w_i { + continue; + } + let px = tile[(row * 8 + col) as usize]; + if px != 0 { + dst[dst_w * y as usize + x as usize] = px; + } + } + } +} diff --git a/client/src/input.rs b/client/src/input.rs new file mode 100755 index 0000000..44111af --- /dev/null +++ b/client/src/input.rs @@ -0,0 +1,83 @@ +use pbio::Key; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GameAction { + Up, + Down, + Left, + Right, + Confirm, + Cancel, +} + +pub struct InputMap { + bindings: Vec<(Key, GameAction)>, +} + +impl InputMap { + pub fn new() -> Self { + Self { + bindings: vec![ + (Key::Up, GameAction::Up), + (Key::Down, GameAction::Down), + (Key::Left, GameAction::Left), + (Key::Right, GameAction::Right), + (Key::W, GameAction::Up), + (Key::S, GameAction::Down), + (Key::A, GameAction::Left), + (Key::D, GameAction::Right), + (Key::Enter, GameAction::Confirm), + (Key::Escape, GameAction::Cancel), + ], + } + } + + pub fn translate(&self, key: Key) -> Option { + self.bindings.iter().find(|(k, _)| *k == key).map(|(_, a)| *a) + } +} + +pub struct InputState { + pressed: Vec, + held: Vec, + released: Vec, +} + +impl InputState { + pub fn new() -> Self { + Self { pressed: Vec::new(), held: Vec::new(), released: Vec::new() } + } + + pub fn push(&mut self, action: GameAction) { + self.pressed.push(action); + if !self.held.contains(&action) { + self.held.push(action); + } + } + + pub fn release(&mut self, action: GameAction) { + self.held.retain(|a| *a != action); + if !self.released.contains(&action) { + self.released.push(action); + } + } + + pub fn button_pressed(&self, action: GameAction) -> bool { + self.pressed.contains(&action) + } + + #[allow(dead_code)] + pub fn button_held(&self, action: GameAction) -> bool { + self.held.contains(&action) + } + + #[allow(dead_code)] + pub fn button_released(&self, action: GameAction) -> bool { + self.released.contains(&action) + } + + pub fn clear(&mut self) { + self.pressed.clear(); + self.released.clear(); + } +} diff --git a/client/src/main.rs b/client/src/main.rs new file mode 100755 index 0000000..b77bf68 --- /dev/null +++ b/client/src/main.rs @@ -0,0 +1,60 @@ +use pbio::{Event, Platform, PlatformConfig}; +use std::time::{Duration, Instant}; + +mod assets; +mod game; +mod input; +mod net; + +fn main() { + let mut plat = Platform::new(PlatformConfig { + title: "Roguelike".into(), + window_size: (800, 600), + framebuffer_size: (320, 240), + aspect_ratio: Some(4.0 / 3.0), + palette: pbio::palette::RGB332, + vsync: true, + mouse_capture: false, + mouse_visible: true, + }); + + let input_map = input::InputMap::new(); + let mut input_state = input::InputState::new(); + let mut last_update = Instant::now(); + let mut game = game::Game::start(); + + while !plat.should_close() { + plat.poll_events(Some(Duration::from_millis(20))); + + let events: Vec = plat.drain_events().collect(); + for ev in events { + match ev { + Event::Key { key, pressed: true, .. } => { + if let Some(action) = input_map.translate(key) { + input_state.push(action); + } + } + Event::Key { key, pressed: false, .. } => { + if let Some(action) = input_map.translate(key) { + input_state.release(action); + } + } + Event::CloseRequested => plat.request_close(), + _ => {} + } + } + + let dt = last_update.elapsed().as_millis(); + if dt < 16 { continue; } + last_update = Instant::now(); + + if let Some(game::GameSignal::Quit) = + game.update(plat.framebuffer_mut(), dt as usize, &input_state) + { + plat.request_close(); + } + input_state.clear(); + + plat.present(); + } +} diff --git a/client/src/net.rs b/client/src/net.rs new file mode 100755 index 0000000..518153d --- /dev/null +++ b/client/src/net.rs @@ -0,0 +1,208 @@ +use std::collections::HashMap; +use std::mem::size_of; +use std::net::{SocketAddr, UdpSocket}; +use std::time::{Duration, Instant}; +use bytemuck::bytes_of; +use shared::{ActionPacket, ChunkEntry, ChunkPacket, EntityPacket, + Header, PingPacket, PongPacket, StatePacket, + MAGIC, VERSION, packet_type}; + +pub struct CachedChunk { + pub version: u16, + pub tiles: Box<[u16; 1024]>, // decoded global tile IDs +} + +pub struct EntityInfo { + pub id: u32, + pub type_id: u16, + pub pos_x: i16, + pub pos_y: i16, + pub hp: u16, + pub hp_max: u16, + pub flags: u8, +} + +pub enum NetEvent { + Pong { rtt_ms: u64 }, + State { tick: u32, player_entity_id: u32 }, + Chunk { chunk_id: u32 }, + Entity { entities: Vec }, + Disconnected, +} + +const FNV_BASIS: u32 = 2_166_136_261; +const FNV_PRIME: u32 = 16_777_619; + +pub struct NetClient { + socket: UdpSocket, + ping_sent_at: Option, + action_sequence: u32, + last_action_sent: Instant, + last_state_received: Option, + pub chunk_cache: HashMap, + last_state_chunks: Option<[ChunkEntry; 9]>, + pending_entities: Vec, + last_entity_checksum: u32, + last_state_tick: u32, + entity_checksum_accum: u32, +} + +impl NetClient { + pub fn new(server_addr: SocketAddr) -> Self { + let socket = UdpSocket::bind("0.0.0.0:0").expect("bind failed"); + socket.connect(server_addr).expect("connect failed"); + socket.set_nonblocking(true).expect("set_nonblocking failed"); + Self { + socket, + ping_sent_at: None, + action_sequence: 0, + last_action_sent: Instant::now(), + last_state_received: None, + chunk_cache: HashMap::new(), + last_state_chunks: None, + pending_entities: Vec::new(), + last_entity_checksum: 0, + last_state_tick: 0, + entity_checksum_accum: FNV_BASIS, + } + } + + pub fn send_action(&mut self, player_action: u16) { + self.action_sequence += 1; + let packet = ActionPacket { + header: Header::new(packet_type::ACTION), + auth_token: 0, + sequence: self.action_sequence, + cache: { + let mut cache = [ChunkEntry { chunk_id: 0, version: 0 }; 9]; + if let Some(slots) = self.last_state_chunks { + for (i, slot) in slots.iter().enumerate() { + let cid = slot.chunk_id; + let ver = self.chunk_cache.get(&cid) + .map(|c| c.version).unwrap_or(0); + cache[i] = ChunkEntry { chunk_id: cid, version: ver }; + } + } + cache + }, + player_action, + }; + self.socket.send(bytes_of(&packet)).ok(); + self.last_action_sent = Instant::now(); + } + + pub fn send_ping(&mut self) { + let ping = PingPacket { header: Header::new(packet_type::PING), timestamp_ms: 0 }; + self.ping_sent_at = Some(Instant::now()); + self.socket.send(bytes_of(&ping)).ok(); + } + + pub fn poll(&mut self) -> Vec { + const KEEPALIVE: Duration = Duration::from_secs(2); + const TIMEOUT: Duration = Duration::from_secs(10); + + if self.last_action_sent.elapsed() >= KEEPALIVE { + self.send_action(shared::player_action::NOOP); + } + + let mut events = Vec::new(); + let mut buf = [0u8; 1200]; + + loop { + match self.socket.recv(&mut buf) { + Ok(n) => { + if n < size_of::
() { continue; } + let header: Header = *bytemuck::from_bytes(&buf[..size_of::
()]); + if header.magic != MAGIC || header.version != VERSION { continue; } + + match header.packet_type { + packet_type::PONG if n >= size_of::() => { + if let Some(sent_at) = self.ping_sent_at.take() { + let rtt_ms = sent_at.elapsed().as_millis() as u64; + events.push(NetEvent::Pong { rtt_ms }); + } + } + packet_type::STATE if n >= size_of::() => { + let pkt: StatePacket = *bytemuck::from_bytes(&buf[..size_of::()]); + self.last_state_received = Some(Instant::now()); + self.last_state_chunks = Some(pkt.chunks); + self.last_entity_checksum = pkt.entity_checksum; + self.last_state_tick = pkt.tick; + self.pending_entities.clear(); + self.entity_checksum_accum = FNV_BASIS; + events.push(NetEvent::State { tick: pkt.tick, player_entity_id: pkt.player_entity_id }); + } + packet_type::ENTITY if n >= size_of::() => { + let pkt: EntityPacket = *bytemuck::from_bytes(&buf[..size_of::()]); + if pkt.tick != self.last_state_tick { + // stale datagram from a previous state cycle + } else { + for i in 0..pkt.entity_count as usize { + let e = pkt.entities[i]; + for byte in bytemuck::bytes_of(&e) { + self.entity_checksum_accum ^= *byte as u32; + self.entity_checksum_accum = + self.entity_checksum_accum.wrapping_mul(FNV_PRIME); + } + self.pending_entities.push(EntityInfo { + id: e.id, + type_id: e.type_id, + pos_x: e.pos_x, + pos_y: e.pos_y, + hp: e.hp, + hp_max: e.hp_max, + flags: e.entity_flags, + }); + } + if pkt.packet_flags & 1 == 0 { + // last datagram in sequence + if self.entity_checksum_accum != self.last_entity_checksum { + self.send_action(shared::player_action::NOOP); + } + events.push(NetEvent::Entity { + entities: std::mem::take(&mut self.pending_entities), + }); + } + } + } + packet_type::CHUNK if n >= size_of::() => { + let pkt: ChunkPacket = *bytemuck::from_bytes(&buf[..size_of::()]); + let id = pkt.chunk.chunk_id; + let version = pkt.chunk.version; + let palette = pkt.palette; + let raw = pkt.tiles; + let mut tiles = Box::new([0u16; 1024]); + for i in 0..256usize { + let b0 = raw[i * 3]; + let b1 = raw[i * 3 + 1]; + let b2 = raw[i * 3 + 2]; + let ia = (b0 & 0x3F) as usize; + let ib = ((b0 >> 6) | ((b1 & 0x0F) << 2)) as usize; + let ic = ((b1 >> 4) | ((b2 & 0x03) << 4)) as usize; + let id_ = (b2 >> 2) as usize; + tiles[i * 4] = palette[ia]; + tiles[i * 4 + 1] = palette[ib]; + tiles[i * 4 + 2] = palette[ic]; + tiles[i * 4 + 3] = palette[id_]; + } + self.chunk_cache.insert(id, CachedChunk { version, tiles }); + events.push(NetEvent::Chunk { chunk_id: id }); + } + _ => {} + } + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break, + Err(_) => break, + } + } + + if let Some(t) = self.last_state_received { + if t.elapsed() >= TIMEOUT { + self.last_state_received = None; + events.push(NetEvent::Disconnected); + } + } + + events + } +} diff --git a/notes/01-input_manager.md b/notes/01-input_manager.md new file mode 100755 index 0000000..e29cf39 --- /dev/null +++ b/notes/01-input_manager.md @@ -0,0 +1,68 @@ +# Input Manager + +## Current state + +Input is fully decoupled from the windowing backend. `client/src/input.rs` owns all input +types. `main.rs` translates `pbio::Event::Key` events into `GameAction` values each frame and +stores them in an `InputState`. `game::update()` receives a `&InputState` and queries it with +three distinct semantics. (The platform/windowing layer lives in the external `pbio` crate; +game code only ever sees `pbio::Key`, never a raw backend type.) + +## Architecture + +### `GameAction` — `client/src/input.rs` + +Logical actions the game cares about. Game code never sees backend key types. + +```rust +pub enum GameAction { Up, Down, Left, Right, Confirm, Cancel } +``` + +### `InputMap` — `client/src/input.rs` + +Translates a `pbio::Key` to an `Option`. All key bindings live here. + +| Key | Action | +|------------------|---------| +| Up / W | Up | +| Down / S | Down | +| Left / A | Left | +| Right / D | Right | +| Enter | Confirm | +| Escape | Cancel | + +### `InputState` — `client/src/input.rs` + +Three internal buffers, populated from `pbio::Event::Key` events in `main.rs`: + +| Buffer | Lifetime | Populated by | +|------------|----------------|---------------------------| +| `pressed` | current frame | key-down event | +| `held` | until released | key-down; cleared on up | +| `released` | current frame | key-up event | + +`clear()` is called after `game::update()` each frame. It clears `pressed` and `released`; +`held` persists until the corresponding key-up event arrives. + +#### Query API + +```rust +input.button_pressed(action) // true only on the frame the key went down +input.button_held(action) // true every frame the key is physically held +input.button_released(action) // true only on the frame the key was released +``` + +## Data flow per frame + +``` +pbio::Event::Key + → InputMap::translate(key) + → InputState::push / InputState::release + → game.update(&mut framebuffer, dt, &input_state) -> Option + → input_state.clear() +``` + +## Open questions + +- Should modifier keys (Shift, Ctrl) produce distinct actions, or be handled in the mapping? +- How do we prepare for key rebinding? diff --git a/notes/02-asset_loader.md b/notes/02-asset_loader.md new file mode 100755 index 0000000..cacfd75 --- /dev/null +++ b/notes/02-asset_loader.md @@ -0,0 +1,35 @@ +# Asset Loader + +## Current state + +TGA loading is implemented in `client/src/assets.rs`. `Image::from_tga(path)` reads the file, +parses the 18-byte header, skips the colormap block, and returns an `Image` — supporting +type 1 (uncompressed) and type 9 (RLE) via the private `decode_rle()` function in the same +module. `game.rs` uses it directly: `Image::from_tga("assets/tilesets/overworld.tga")`. + +`Image` also provides `to_tileset() -> Vec<[u8; 64]>`, which splits the image into 8×8 +tiles row-major. Every tile is emitted; tile ID is the flat row-major index into the Vec, +so tiles are never skipped or reordered. (Transparency is a per-pixel concern handled at +blit time by `blit_tile`, not by the splitter.) + +`AssetStore` (a named registry) is **not yet implemented**. Assets are currently loaded +inline in `Game::start()`. + +## Goal + +Add an `AssetStore` so that game code can ask for an asset by name rather than loading +files directly at the call site. + +## Design notes + +- `Image` struct: `width: u32`, `height: u32`, `pixels: Vec` — done. +- A simple `AssetStore` could be a `HashMap<&'static str, Image>` loaded at startup. +- No streaming needed — the whole game is small enough to load everything upfront in `start()`. +- Palette index 0 is transparent when blitting sprites. This convention is enforced by + `blit_tile()` in `pixelhelper.rs`; the loader itself does not need to handle it. +- Pixel assets are stored as flat `Vec` of palette indices, row-major, width × height bytes. +- Future: consider embedding assets with `include_bytes!` to produce a single binary. + +## Open questions + +- Should `AssetStore` hold pre-split tilesets too, or just raw `Image` values? diff --git a/notes/04-text_renderer.md b/notes/04-text_renderer.md new file mode 100755 index 0000000..4a2dbc9 --- /dev/null +++ b/notes/04-text_renderer.md @@ -0,0 +1,31 @@ +# Text Renderer + +## Current state + +No text rendering exists yet. No font loading or glyph blitting code exists. + +## Goal + +Draw ASCII text into the `u8` framebuffer using a bitmap font, respecting the palette-index +color model (one u8 per pixel, no RGBA). + +## Design notes + +- A bitmap font is the right fit: each glyph is a small tile of palette indices, blitted + directly into the framebuffer using the existing blit primitive in `pixelhelper.rs`. +- Likely approach: a fixed 8×8 or similar glyph grid packed into a TGA or raw binary, + one glyph per ASCII codepoint starting at 0x20 (space). +- The font image itself should be indexed: foreground pixels carry a nonzero palette index + (caller supplies the color), background pixels are index 0 (transparent/skip). +- API sketch: + ```rust + fn draw_text(frame: &mut [u8], font: &Font, x: i32, y: i32, color: u8, text: &str); + ``` +- `Font` holds the glyph sheet as an `Image` plus glyph width/height. + +## Open questions + +- How do we handle proportional spacing of different characters? +- How do we store font data in memory? +- How do we handle font color rendering? +- How do we handle special characters like Ä, Ö, Ü, ß, etc.? diff --git a/notes/05-ui_renderer.md b/notes/05-ui_renderer.md new file mode 100755 index 0000000..65b1f99 --- /dev/null +++ b/notes/05-ui_renderer.md @@ -0,0 +1,36 @@ +# UI Renderer + +## Current state + +No UI primitives exist. The framebuffer is currently filled by the game world only +(`render_viewport` in `game.rs`: tile pass + entity sprite pass). `pixelhelper.rs` has +low-level pixel/blit helpers but nothing higher-level. + +## Goal + +Draw common roguelike UI elements (panels, borders, HUD bars, etc.) procedurally into the +`u8` framebuffer, composited on top of the game world. + +## Design notes + +The virtual resolution is 320×240, so UI layout should be designed in those pixel units. + +Likely primitives needed (built on top of `pixelhelper.rs`): + +- `fill_rect(frame, x, y, w, h, color)` — solid filled rectangle +- `draw_rect(frame, x, y, w, h, color)` — 1-pixel border rectangle +- `draw_border_box(frame, x, y, w, h, tileset)` — box drawn with corner/edge tiles from + a tileset (classic roguelike panel look) +- `draw_hbar(frame, x, y, w, value, max, fg, bg)` — horizontal progress/HP bar + +Panels are typically fixed regions of the screen (e.g. a status bar at the bottom 40px, +a message log on the right). Hard-coding these regions first is fine; extract to a layout +system only if needed. + +The UI layer draws after the world layer so it always appears on top. Draw order within +the UI should be back-to-front (backgrounds before text). + +## Open questions + +- Tileset-based borders vs. line-drawing: which look are we going for? +- Does the message log need scrolling? Probably not at first. diff --git a/notes/06-hybrid-tick-loop.md b/notes/06-hybrid-tick-loop.md new file mode 100755 index 0000000..5ae1d38 --- /dev/null +++ b/notes/06-hybrid-tick-loop.md @@ -0,0 +1,117 @@ +# Hybrid Tick Loop + +Single 24 Hz base tick; subsystems self-select frequency via stride scheduling. Replaces the old +3-tier (20/10/1 Hz buckets) description from roadmap item 06. + +--- + +## Stride scheduling + +All subsystems hang off one monotonic `tick: u32` counter. Each subsystem fires when its +stride condition is met. **Current implementation:** + +``` +tick % 1 == 0 → input processing, inbound recv (main.rs) +tick % 2 == send_phase → broadcast state to clients, phase-staggered (net.rs, 12 Hz/client) +tick % 4 == 0 → entity_tick() — movement / collision (sim.rs, 6 Hz) +tick % 8 == 0 → (reserved) slower AI routines, pathfinding refresh +tick % 192 == 0 → (reserved) world simulation (8-sec cycle: weather, daylight, respawns) +``` + +24 Hz is highly composite (divisors: 1, 2, 3, 4, 6, 8, 12, 24), giving flexible stride +options without resorting to co-prime tricks. `tick >> 3` yields 3 Hz pulses; world-sim +at `% 192` is exactly an 8-second cycle. + +**Why `entity_tick` runs at 6 Hz, not 12 Hz:** without the action-point model (below), one +`entity_tick` = one tile of movement, so the tick rate *is* the movement rate. 6 Hz yields the +target ~6 tiles/sec. Once `energy`/`speed` are reintroduced, `entity_tick` can move up to 12 Hz +(`% 2`) and per-entity `speed` sets the effective movement rate instead — decoupling sim rate +from movement rate. The broadcast rate (12 Hz/client) is deliberately higher than the movement +rate: it keeps position latency low and adds redundancy against packet loss. + +--- + +## Action-point model (entity AI) + +> **Status: not yet implemented.** Removed from `entity.rs` as premature (no NPCs need it yet). +> Until it returns, `entity_tick` runs at 6 Hz to set the movement rate directly (see above). +> Reintroduce alongside roadmap item 08 (NPCs). + +Layered on top of `entity_tick()`. Replaces fixed per-entity cooldown timers. + +Each entity carries: +- `energy: i32` — accumulates each tick +- `speed: u8` — added to energy every `entity_tick()` + +An entity acts when `energy >= ACTION_COST`, then pays the cost: + +```rust +// inside entity_tick() +entity.energy += entity.speed as i32; +if entity.energy >= ACTION_COST { + entity.act(&mut world); + entity.energy -= ACTION_COST; +} +``` + +Fast entities (`speed >= ACTION_COST`) act every tick. Slow entities act every N ticks +naturally without any scheduler involvement. Haste and slow effects become simple `speed` +modifiers — no special-case scheduling needed. + +--- + +## Simulation rate vs network rate + +Sim and net rates are independent. The current targets: + +| Layer | Rate | Stride | +|-------|------|--------| +| Base loop | 24 Hz | every tick | +| Network broadcast (per client, phase-staggered) | 12 Hz | `tick % 2 == send_phase` | +| Entity tick (movement / collision) | 6 Hz | `tick % 4 == 0` | + +Clients receive a fresh `EntityPacket` every ~83 ms. For tile-based movement at 5–6 tiles/sec +this is sufficient with comfortable headroom. + +Rough bandwidth per client at 12 Hz: +- `StatePacket` 72 B × 12 = 864 B/s +- `EntityPacket` ~1200 B × 12 = 14 400 B/s +- Total: ~15 KB/s outbound per client + +--- + +## Tick-offset broadcasting (phase staggering) + +Rather than flushing all clients on the same tick, assign each client a `send_phase` at +connection time and send only when the client's phase matches: + +```rust +// ClientState gains: +send_phase: u8, // assigned as entity_id % BROADCAST_STRIDE at creation + +// broadcast() skips clients whose phase doesn't match current tick: +if tick % BROADCAST_STRIDE as u32 != cs.send_phase as u32 { continue; } +``` + +Benefits: +- Outbound NIC load is flat across ticks instead of spiking every 2nd tick +- Scales with player count without architectural changes +- Each client still receives state at the same effective rate + +--- + +## Why higher tick rate doesn't help high-ping players + +``` +Perceived latency ≈ RTT + tick_processing_delay + state_interval + +LTE @ 100ms RTT, 24Hz sim, 12Hz net: + worst case: 100 + 42 + 83 = 225ms ← RTT-dominated + +Same at 64Hz sim, 20Hz net: + worst case: 100 + 15 + 50 = 165ms ← 60ms gain, negligible for roguelike +``` + +RTT is the dominant term. Higher tick rates yield diminishing returns and increase server +CPU load for minimal perceived benefit. 24 Hz is sufficient for deliberate tile-based input +and remains acceptable at LTE latencies (~100ms RTT) and remote locations (~200ms RTT). diff --git a/notes/protocol.md b/notes/protocol.md new file mode 100755 index 0000000..d7a5fec --- /dev/null +++ b/notes/protocol.md @@ -0,0 +1,310 @@ +# Protocol +Wire format for client-server communication. + +## Transport + +- UDP (`std::net::UdpSocket`, non-blocking) +- Hard limit: **1200 bytes per datagram** (safe internet MTU; avoids IP fragmentation) +- All integers: little-endian +- Server streams `StatePackets` continuously; client sends `ActionPackets` only when needed + +--- + +## Header + +Every datagram begins with a 6-byte universal header. + +| Offset | Size | Type | Field | Notes | +|--------|------|------|---------------|-----------------------------------------| +| 0 | 2 | u16 | `magic` | Fixed: `0x524C` ("RL") — rejects noise | +| 2 | 1 | u8 | `version` | Protocol version — server rejects mismatch | +| 3 | 1 | u8 | `packet_type` | See packet type table below | +| 4 | 2 | u16 | `client_type` | 0 = official client; others registered | + +`client_type` allows the server to distinguish official clients, registered bots, and +forks for analytics and access control, without affecting the protocol logic. + +--- + +## Packet types + +| Packet type | id | Flow | Size | +|---------------|----|------------------|------------------| +| State | 0 | server → client | 72 bytes | +| Action | 1 | client → server | min. 74 bytes | +| Chunk Data | 2 | server → client | max. 909 bytes | +| Entity | 3 | server → client | max. 1200 bytes | +| Entity Query | 4 | client → server | 18 bytes | +| Entity Detail | 5 | server → client | max. 1200 bytes | +| Ping | 6 | client → server | 14 bytes | +| Pong | 7 | server → client | 14 bytes | + +--- + +### StatePacket — server → client + +Sent by the server at 10 Hz regardless of client activity. +Currently carries the chunk manifest for the 3×3 neighbourhood around the player. + +| Offset | Size | Type | Field | Notes | +|--------|------|------------|---------------------|-------------------------------------------------| +| 0 | 6 | Header | `header` | packet_type = 0 | +| 6 | 4 | u32 | `tick` | Server tick counter | +| 10 | 54 | ChunkEntry | `chunks[9]` | 9 chunk entries | +| 64 | 4 | u32 | `player_entity_id` | Global entity ID of the receiving client | +| 68 | 4 | u32 | `entity_checksum` | FNV-1a over all EntityPacket payloads this tick | + +**Total: 72 bytes** + +The `entity_checksum` lets the client detect a lost `EntityPacket` without a dedicated +ACK: if the checksum differs from the one computed over the last received entity update, +the client knows to retransmit an `ActionPacket` (sequence preserved, no-op action) to +prompt the server to re-send the current entity state. + +--- + +### ActionPacket — client → server + +Sent by the client on player action or on a chunk cache miss. + +| Offset | Size | Type | Field | Notes | +|--------|------|--------------|-----------------|--------------------------------| +| 0 | 6 | Header | `header` | packet_type = 1 | +| 6 | 8 | u64 | `auth_token` | Token of the current session | +| 14 | 4 | u32 | `sequence` | Monotonically increasing | +| 18 | 54 | ChunkEntry | `cache[9]` | Versions client currently holds | +| 72 | 2 | PlayerAction | `player_action` | Derived from user input | +| 74 | ? | ActionData | `action_data` | Dependent on PlayerAction | + +**Minimum: 74 bytes** (no ActionData) + +**ChunkEntry (6 bytes)** + +| Offset | Size | Type | Field | +|--------|------|------|------------| +| 0 | 4 | u32 | `chunk_id` | +| 4 | 2 | u16 | `version` | + +`chunk_id` encodes the chunk grid position: `(x as u16) | ((y as u16) << 16)`, +where x and y are signed chunk coordinates (i16 each). + +**PlayerAction (u16)** + +| Value | Action | +|-------|---------| +| 0 | No-Op (cache update only) | +| 1 | North | +| 2 | East | +| 3 | South | +| 4 | West | + +$TODO — additional actions (interact, etc.) + +--- + +### ChunkPacket — server → client + +Sent by the server for each chunk the client is missing or has stale. +One chunk per datagram. + +The world is divided into **32×32 tile chunks**. +- At most **4 chunks (2×2)** are visible at once. +- The 3×3 neighbourhood (9 chunks) covers all prefetch needs. +- Each chunk carries a **local palette** of up to 64 tile types. +- Tiles are encoded as 6-bit palette indices (4 tiles per 3 bytes, no padding). + +| Offset | Size | Type | Field | Notes | +|-------------------|----------|--------------|-------------|---------------------------------| +| 0 | 6 | Header | `header` | packet_type = 2 | +| 6 | 6 | ChunkEntry | `chunk` | ID and version of this chunk | +| 12 | 1 | u8 | `pal_count` | Number of palette entries (≤64) | +| 13 | max. 128 | u16[] | `palette` | Global tile IDs, pal_count × 2 | +| 13 + pal_count×2 | 768 | packed u6[] | `tiles` | 1024 tiles, 6-bit indices | + +**Maximum: 6 + 6 + 1 + 128 + 768 = 909 bytes** + +**Palette** — up to 64 entries, each a global tile ID (u16) mapping local 6-bit index → world tile type. + +**Tiles** — 1024 tiles packed as 256 groups of 3 bytes (4 tiles × 6 bits = 24 bits per group). + +--- + +### EntityPacket — server → client + +Sent by the server each tick, immediately after the `StatePacket`. +Contains the bulk entity update for all entities within the visible **30×30 tile viewport**. +Positions are **absolute world tile coordinates**. + +If the entity count exceeds what fits in one datagram, the server sends multiple +`EntityPacket`s on the same tick; `packet_flags` bit 0 signals that more follow. + +| Offset | Size | Type | Field | Notes | +|--------|----------------|-------------|----------------|--------------------------------| +| 0 | 6 | Header | `header` | packet_type = 3 | +| 6 | 4 | u32 | `tick` | Matches the `StatePacket` tick | +| 10 | 1 | u8 | `entity_count` | Entities in this datagram | +| 11 | 1 | u8 | `packet_flags` | bit 0: more packets follow | +| 12 | entity_count×… | EntityEntry | `entities` | Variable-length entries | + +**Maximum: 1200 bytes** — without metadata: ⌊(1200 − 12) / 18⌋ = **66 entities per datagram**. + +--- + +#### EntityEntry + +| Offset | Size | Type | Field | Notes | +|--------|------------|------|-----------------|-----------------------------------------| +| 0 | 4 | u32 | `id` | Server-assigned entity ID (persistent) | +| 4 | 2 | u16 | `type_id` | Entity type; 0 = player | +| 6 | 2 | i16 | `pos_x` | Absolute world tile X | +| 8 | 2 | i16 | `pos_y` | Absolute world tile Y | +| 10 | 2 | u16 | `hp` | Current HP | +| 12 | 2 | u16 | `hp_max` | Max HP | +| 14 | 2 | u16 | `elo` | Elo rating (acts as level) | +| 16 | 1 | u8 | `entity_flags` | See flag table below | +| 17 | 1 | u8 | `meta_len` | Byte length of the TLV metadata block | +| 18 | `meta_len` | u8[] | `meta` | TLV metadata (0 bytes if none) | + +**Base size: 18 bytes** (plus `meta_len` bytes of metadata). + +**entity_flags:** + +| Bit | Meaning | +|-----|-------------------------------------------------------| +| 0 | `is_static` — does not move (chest, sign, item stack) | +| 1 | `is_hostile` | +| 2 | `is_interactable` — show interaction prompt | +| 3–7 | reserved | + +--- + +#### Inline metadata — TLV format + +Inline metadata is a sequence of TLV (type–length–value) fields appended directly after +the fixed `EntityEntry` fields. `meta_len = 0` means no metadata is present. + +Each TLV field: + +| Offset | Size | Type | Field | +|--------|------|------|---------| +| 0 | 1 | u8 | `tag` | +| 1 | 1 | u8 | `len` | +| 2 | len | u8[] | `value` | + +**Defined tags:** + +| Tag | Name | Value format | Notes | +|------|------------------|--------------|--------------------------------| +| 0x01 | `status_effects` | u16 bitmask | Active status effects | +| 0x02 | `weapon` | u16 type_id | Currently equipped weapon | +| 0x03 | `armor` | u16 type_id | Currently equipped armor | + +Large metadata (chest inventory, sign text, dialogue trees) is not included inline. +Request it via `EntityQueryPacket` (type 4) on player interaction. + +--- + +### EntityQueryPacket — client → server + +Sent when the client needs detailed metadata for a specific entity (on interaction, +hover, or similar trigger). The server responds with an `EntityDetailPacket`. + +| Offset | Size | Type | Field | Notes | +|--------|------|--------|--------------|------------------------------| +| 0 | 6 | Header | `header` | packet_type = 4 | +| 6 | 8 | u64 | `auth_token` | Token of the current session | +| 14 | 4 | u32 | `entity_id` | Entity to query | + +**Total: 18 bytes** + +--- + +### EntityDetailPacket — server → client + +Full metadata response for a queried entity. Sent in reply to an `EntityQueryPacket`. + +| Offset | Size | Type | Field | Notes | +|--------|------------|--------|--------------|------------------------------| +| 0 | 6 | Header | `header` | packet_type = 5 | +| 6 | 4 | u32 | `tick` | Server tick at time of query | +| 10 | 4 | u32 | `entity_id` | Entity being described | +| 14 | 2 | u16 | `meta_len` | Byte length of metadata | +| 16 | `meta_len` | u8[] | `meta` | Full TLV metadata block | + +**Maximum: 1200 bytes** (up to 1184 bytes of metadata). + +--- + +### PingPacket / PongPacket — round-trip measurement + +Liveness and RTT probe. The client sends a `PingPacket`; the server echoes the payload back +unchanged as a `PongPacket`. Both share the same layout. + +| Offset | Size | Type | Field | Notes | +|--------|------|--------|----------------|--------------------------------------| +| 0 | 6 | Header | `header` | packet_type = 6 (Ping) / 7 (Pong) | +| 6 | 8 | u64 | `timestamp_ms` | Echoed verbatim by the server | + +**Total: 14 bytes** + +The client currently measures RTT locally via the elapsed time since the ping was sent, so +`timestamp_ms` is sent as 0; the field is reserved for server-stamped timing if needed later. + +--- + +## Protocol flow + +``` +── every tick (10 Hz) ──────────────────────────────────────────────────────── + +server → client StatePacket (tick N, chunk manifest, entity_checksum) +server → client EntityPacket (tick N, entities 0–73) +server → client EntityPacket (tick N, entities 74–N, more=0) ← if needed + + ↓ client verifies entity_checksum + ↓ checksum mismatch → retransmit ActionPacket (no-op) + ↓ chunk cache miss on chunk X → + +client → server ActionPacket (auth_token, seq, cache state, player_action) + +server → client ChunkPacket (chunk X, full data) +server → client ChunkPacket (chunk Y, full data) ← if multiple misses + +── on player interaction ───────────────────────────────────────────────────── + +client → server EntityQueryPacket (auth_token, entity_id) +server → client EntityDetailPacket (entity_id, full TLV metadata) +``` + +The server reads the `ActionPacket`'s cache list, computes the diff against current +chunk versions, and sends one `ChunkPacket` per missing or stale chunk. + +--- + +## Reliability + +UDP is unreliable. The protocol handles loss without a dedicated ACK mechanism: + +**Lost ActionPacket** — the server never learns of the cache miss and keeps streaming +StatePackets. The client retransmits the ActionPacket after **200–300 ms** if no +ChunkPacket has arrived. + +**Lost ChunkPacket** — the server has already processed the ActionPacket and will not +retransmit spontaneously. The client's timeout fires (200–300 ms), it resends the +ActionPacket with its updated cache state (listing only still-missing chunks), and +the server sends the missing chunks again. + +**Duplicate ActionPackets** — the server handles these idempotently. The cache list is +self-describing state; the server reads it, diffs against current versions, and sends +whatever is still missing. No deduplication logic is required. + +**Lost StatePacket** — the next tick delivers the same chunk manifest. No retry needed; +the client simply waits one tick (~100 ms). + +**Lost EntityPacket** — detected via the `entity_checksum` in the next `StatePacket`. +The client retransmits a no-op `ActionPacket` (same sequence number, no-op action, +current cache state); the server treats this as a normal diff request and re-sends the +full entity update for the tick. + +**Lost EntityQueryPacket / EntityDetailPacket** — the client retransmits the query +after a 200–300 ms timeout if no `EntityDetailPacket` has arrived. diff --git a/notes/roadmap.md b/notes/roadmap.md new file mode 100755 index 0000000..db34575 --- /dev/null +++ b/notes/roadmap.md @@ -0,0 +1,122 @@ +# Roadmap + +Workspace layout: `shared/` (wire types), `server/` (authoritative sim), `client/` (render +terminal). The platform layer — window, GPU, input, RGB332 palette — lives in the external +`pbio` crate (git dependency), not in this repo. + +--- + +## Achieved milestones + +- [x] TGA loader: `Image`, `from_tga`, `to_tileset`, `decode_rle` in `client/src/assets.rs` +- [x] Pixel helpers: `set_pixel`, `extract`, `blit`, `blit_tile` in `client/src/game/pixelhelper.rs` + — signed `i32` offsets, clip against all four framebuffer edges, index 0 = transparent +- [x] Input system: `GameAction`, `InputMap`, `InputState` in `client/src/input.rs` (over `pbio::Key`) +- [x] Protocol spec: `notes/protocol.md` — Header, State, Action, Chunk, Entity, EntityQuery, + EntityDetail, Ping/Pong; reliability model +- [x] Shared crate: packet structs as `bytemuck::Pod` with compile-time size assertions, + `player_action` / `packet_type` constants, `chunk_id` / `chunk_coords` +- [x] Server binary: 24 Hz tick loop, 3×3-chunk test world (`build_world`), wall collision, + per-client action queue, `ChunkPacket` dispatch on cache miss, stale-client eviction +- [x] Client net: `NetClient` in `client/src/net.rs`, non-blocking UDP recv, + `StatePacket` / `EntityPacket` / `ChunkPacket` / `PongPacket` dispatch, entity-checksum retransmit +- [x] End-to-end loop: player moves on server, position reflected in `EntityPacket`, rendered on client +- [x] Tile rendering: chunk-based world rendering from received `ChunkPacket` data (6-bit unpack) +- [x] Tick-based movement: movement resolved in `entity_tick`, currently every 4th tick (`sim.rs`) +- [x] Sprite-based entity rendering: entity `type_id` → tile in `entities.tga`, blitted with transparency +- [x] Phase-staggered broadcast: per-client `send_phase`, flat outbound load across ticks +- [x] Ping/Pong RTT measurement (`PingPacket` / `PongPacket`) +- [x] Connection timeout: client emits `Disconnected` after 10 s without a `StatePacket`; + server evicts clients unseen for 10 s + +--- + +## Upcoming work + +### 06 — Hybrid tick loop *(partially done)* + +Design of record: **`notes/06-hybrid-tick-loop.md`** (24 Hz base tick + stride scheduling). +This supersedes the original 20/10/1 Hz three-tier sketch. + +- [x] 24 Hz base loop (`server/src/main.rs`) +- [x] Phase-staggered broadcast at 12 Hz/client (`send_phase` in `server/src/net.rs`) +- [x] Stride layout settled: `entity_tick` at `%4` (6 Hz, sets movement rate), broadcast at + `%2` (12 Hz/client). The `06` note now documents this and why. +- [ ] Action-point model (`energy` / `speed` per entity, act when `energy >= ACTION_COST`). + Removed from `entity.rs` for now as premature — reintroduce when NPCs (08) actually need it. + Then `entity_tick` can move to `%2` (12 Hz) and `speed` sets the effective movement rate. + +--- + +### 07 — Tiled map loader + +Replace the hardcoded `build_world` wall loop with real level data. + +- Parse Tiled TMX (XML) or JSON export — only the subset actually used +- Define the tile vocabulary: `TileKind` variants map 1:1 to Tiled tile IDs +- Load a hand-authored starting area as the first real level +- Procedural generation comes later; hand-authored first + +--- + +### 08 — Basic NPC entity + AI budget + +One dumb wandering enemy. Validates the simulation architecture before complexity accumulates. +Depends on the action-point model from 06 being reintroduced. + +- Add `EntityKind::Npc` with a `think() -> u32` method returning budget cost +- Per sim-tick: distribute `think_budget = BASE / (clients + 1)` across entities ordered by player proximity +- Complex entities consume more budget; simple ones less. Loop breaks at zero — natural load shedding. +- No framework. No trait objects yet. A match on `EntityKind` is fine. + +```rust +let mut budget: u32 = BASE_BUDGET / (client_count + 1).max(1); +for entity in entities_by_player_proximity() { + if budget == 0 { break; } + budget = budget.saturating_sub(entity.think(&mut world)); +} +``` + +--- + +### 09 — Client camera *(sprite rendering already done)* + +Sprite rendering already landed (see Achieved). What remains is the camera: + +- Smooth camera: lerp between last known and current server position; do not snap + (currently `player_pos` snaps hard to the server position in `game.rs`) +- Store previous + current position per entity, interpolate on render +- Camera math is architectural — affects how entity state is stored. Do it before UI. + +--- + +### Later — UI / HUD + +- Bitmap font renderer: 8×8 glyph sheet TGA, `draw_text(frame, font, x, y, color, text)` — see `notes/04` +- UI primitives in `client/src/ui.rs`: `fill_rect`, `draw_rect`, `draw_hbar` — see `notes/05` +- HUD layout (320×240): HP bar + player name bottom 16px, message log right panel +- A real clip-rect on the blit primitives belongs here — once the viewport and HUD panels are + two distinct regions, the abstraction earns its keep. Not before. + +--- + +### Later — Robustness + auth + +- Auth token handshake: replace source-address identity (`auth_token` field exists but is unused) +- Multi-datagram `EntityPacket`: server currently truncates at 66 entities (`net.rs` TODO) +- Asset embedding: `include_bytes!` for single-binary distribution + +--- + +### Later — World depth + +Once the simulation architecture is stable and a real level exists: + +- Doors, interactive objects (server-authoritative state) +- Combat: melee range check, HP, death, respawn +- Inventory: on-demand via `EntityQueryPacket` / `EntityDetailPacket` +- Day/night and weather on the slow tick — affect visibility, spawns +- Procedural dungeon generation (BSP or cellular automata feeding into the same tile format) + +--- + diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100755 index 0000000..613395c --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2024" + +[dependencies] +shared = { path = "../shared" } +bytemuck = "1.24.0" diff --git a/server/src/chunk.rs b/server/src/chunk.rs new file mode 100755 index 0000000..12d34c0 --- /dev/null +++ b/server/src/chunk.rs @@ -0,0 +1,102 @@ +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +pub struct TileFlags(pub u8); + +impl TileFlags { + pub const COLLIDABLE: u8 = 1 << 0; + pub const OPAQUE: u8 = 1 << 1; + pub const LIQUID: u8 = 1 << 2; + + pub fn collidable(self) -> bool { self.0 & Self::COLLIDABLE != 0 } + pub fn opaque(self) -> bool { self.0 & Self::OPAQUE != 0 } + pub fn liquid(self) -> bool { self.0 & Self::LIQUID != 0 } +} + +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +pub struct TileDef { pub tile_id: u16, pub flags: TileFlags } + +#[derive(Debug)] +pub struct PaletteFullError; + +pub struct Chunk { + pub palette: [TileDef; 64], + pub pal_count: u8, + pub tiles: Box<[u8; 1024]>, + pub version: u16, +} + +impl Chunk { + /// Runtime use: all tiles empty, pal_count=1, version=1. + /// Clients with version=0 will always request this chunk. + pub fn new() -> Self { + Self { + palette: [TileDef::default(); 64], + pal_count: 1, + tiles: Box::new([0u8; 1024]), + version: 1, + } + } + + /// Find existing palette entry or insert a new one. + /// Index 0 is always the null sentinel; this short-circuits for default TileDef. + /// Returns (palette_index, was_new). Does NOT bump version. + fn find_or_insert_raw(&mut self, def: TileDef) -> Result<(u8, bool), PaletteFullError> { + if def == TileDef::default() { + return Ok((0, false)); + } + for i in 1..self.pal_count as usize { + if self.palette[i] == def { + return Ok((i as u8, false)); + } + } + if self.pal_count >= 64 { + return Err(PaletteFullError); + } + let idx = self.pal_count; + self.palette[idx as usize] = def; + self.pal_count += 1; + Ok((idx, true)) + } + + /// World gen path. Calls the closure for every tile coordinate. + /// version=0 so the first StatePacket causes all clients to request it. + pub fn generate(f: impl Fn(u8, u8) -> TileDef) -> Result { + let mut chunk = Self { + palette: [TileDef::default(); 64], + pal_count: 1, + tiles: Box::new([0u8; 1024]), + version: 0, + }; + for ly in 0u8..32 { + for lx in 0u8..32 { + let def = f(lx, ly); + let (idx, _) = chunk.find_or_insert_raw(def)?; + chunk.tiles[ly as usize * 32 + lx as usize] = idx; + } + } + chunk.version = 1; + Ok(chunk) + } + + /// Set a tile at local coords. Bumps version if tile index or palette changed. + pub fn set_tile(&mut self, lx: u8, ly: u8, def: TileDef) -> Result<(), PaletteFullError> { + let (idx, was_new) = self.find_or_insert_raw(def)?; + let slot = &mut self.tiles[ly as usize * 32 + lx as usize]; + let changed = *slot != idx; + *slot = idx; + if changed || was_new { + self.version = self.version.wrapping_add(1); + if self.version == 0 { + self.version = 1; + } + } + Ok(()) + } + + pub fn get_tile(&self, lx: u8, ly: u8) -> TileDef { + self.palette[self.tiles[ly as usize * 32 + lx as usize] as usize] + } + + pub fn tile_flags(&self, lx: u8, ly: u8) -> TileFlags { + self.get_tile(lx, ly).flags + } +} diff --git a/server/src/entity.rs b/server/src/entity.rs new file mode 100755 index 0000000..43b1944 --- /dev/null +++ b/server/src/entity.rs @@ -0,0 +1,19 @@ +pub struct Entity { + pub id: u32, + pub type_id: u16, // 0 = player + pub pos: (i16, i16), + pub hp: u16, + pub hp_max: u16, +} + +impl Entity { + pub fn new(id: u32, type_id: u16, pos: (i16, i16), hp: u16) -> Self { + Self { + id, + type_id, + pos, + hp, + hp_max: hp, + } + } +} diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100755 index 0000000..40e566b --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,62 @@ +mod chunk; +mod entity; +mod world; +mod sim; +mod net; + +use std::thread; +use std::time::{Duration, Instant}; + +use chunk::{Chunk, TileDef, TileFlags}; +use world::World; +use sim::Sim; +use net::Net; + +const TICK_HZ: u32 = 24; +const TICK_DURATION: Duration = Duration::from_micros(1_000_000 / TICK_HZ as u64); + +fn build_world() -> World { + let mut w = World::new(); + for cy in -1i16..=1 { + for cx in -1i16..=1 { + let chunk = Chunk::generate(|lx, ly| { + let (wx, wy) = world::local_to_tile(cx, cy, lx, ly); + let border = wx == -32 || wx == 31 || wy == -32 || wy == 31; + if border { + TileDef { tile_id: 1, flags: TileFlags(TileFlags::COLLIDABLE | TileFlags::OPAQUE) } + } else { + TileDef::default() + } + }).expect("palette overflow during world gen"); + w.set_chunk(cx, cy, chunk); + } + } + w +} + +fn main() { + let world = build_world(); + let mut sim = Sim::new(world); + + let mut net = Net::new("127.0.0.1:7777").expect("failed to bind UDP socket"); + + let mut next_tick = Instant::now() + TICK_DURATION; + let mut tick = 0u32; + loop { + net.drain_recv(); + net.evict_stale(); + + for addr in net.drain_new_clients() { + let entity_id = sim.world.spawn_entity(0, (0, 0), 100); + net.add_client(addr, entity_id); + println!("client connected: {addr} → entity {entity_id}"); + } + + let actions = net.drain_actions(); + sim.tick(tick, &actions); + net.broadcast(&sim.world, tick); + thread::sleep(next_tick.saturating_duration_since(Instant::now())); + next_tick += TICK_DURATION; + tick = tick.wrapping_add(1); + } +} diff --git a/server/src/net.rs b/server/src/net.rs new file mode 100755 index 0000000..f67f457 --- /dev/null +++ b/server/src/net.rs @@ -0,0 +1,241 @@ +use std::collections::HashMap; +use std::mem::size_of; +use std::net::{SocketAddr, UdpSocket}; +use std::sync::mpsc::{self, Receiver, SyncSender}; +use std::thread; +use std::time::Instant; + +use bytemuck::bytes_of; +use shared::{ + ActionPacket, ChunkEntry, ChunkPacket, EntityEntry, EntityPacket, + Header, PingPacket, PongPacket, StatePacket, + MAGIC, VERSION, packet_type, +}; + +use crate::world::World; + +pub struct NetClient { + pub entity_id: u32, + pub last_seen: Instant, + pub send_phase: u8, + pub pending_action: Option, + pub last_cache: [ChunkEntry; 9], +} + +pub struct Net { + clients: HashMap, + new_addrs: Vec, + recv_rx: Receiver<(SocketAddr, Vec)>, + send_tx: SyncSender<(SocketAddr, Vec)>, +} + +impl Net { + pub fn new(addr: &str) -> std::io::Result { + let socket = UdpSocket::bind(addr)?; + + let (recv_tx, recv_rx) = mpsc::sync_channel::<(SocketAddr, Vec)>(256); + let (send_tx, send_rx) = mpsc::sync_channel::<(SocketAddr, Vec)>(256); + + let recv_sock = socket.try_clone()?; + thread::spawn(move || { + let mut buf = [0u8; 1200]; + loop { + match recv_sock.recv_from(&mut buf) { + Ok((n, addr)) => { + if recv_tx.send((addr, buf[..n].to_vec())).is_err() { + break; + } + } + Err(_) => break, + } + } + }); + + let send_sock = socket.try_clone()?; + thread::spawn(move || { + loop { + match send_rx.recv() { + Ok((addr, data)) => { let _ = send_sock.send_to(&data, addr); } + Err(_) => break, + } + } + }); + + // socket is dropped here; send/recv threads own their copies + drop(socket); + + Ok(Net { clients: HashMap::new(), new_addrs: Vec::new(), recv_rx, send_tx }) + } + + pub fn add_client(&mut self, addr: SocketAddr, entity_id: u32) { + self.clients.insert(addr, NetClient { + entity_id, + last_seen: Instant::now(), + send_phase: (entity_id % 2) as u8, + pending_action: None, + last_cache: [ChunkEntry { chunk_id: 0, version: 0 }; 9], + }); + } + + pub fn drain_recv(&mut self) { + loop { + match self.recv_rx.try_recv() { + Ok((addr, data)) => self.handle_packet(addr, data), + Err(_) => break, + } + } + } + + fn handle_packet(&mut self, addr: SocketAddr, data: Vec) { + if data.len() < size_of::
() { + return; + } + let header: Header = bytemuck::pod_read_unaligned(&data[..size_of::
()]); + if header.magic != MAGIC || header.version != VERSION { + return; + } + + if header.packet_type == packet_type::ACTION && data.len() == size_of::() { + let pkt: ActionPacket = bytemuck::pod_read_unaligned(&data); + if let Some(client) = self.clients.get_mut(&addr) { + client.pending_action = Some(pkt.player_action); + client.last_seen = Instant::now(); + client.last_cache = pkt.cache; + } else if !self.new_addrs.contains(&addr) { + self.new_addrs.push(addr); + } + } else if header.packet_type == packet_type::PING && data.len() == size_of::() { + let pkt: PingPacket = bytemuck::pod_read_unaligned(&data); + let pong = PongPacket { + header: Header::new(packet_type::PONG), + timestamp_ms: pkt.timestamp_ms, + }; + let _ = self.send_tx.send((addr, bytes_of(&pong).to_vec())); + } + } + + pub fn drain_new_clients(&mut self) -> Vec { + std::mem::take(&mut self.new_addrs) + } + + pub fn evict_stale(&mut self) { + self.clients.retain(|_, c| c.last_seen.elapsed().as_secs() < 10); + } + + pub fn drain_actions(&mut self) -> Vec<(u32, u16)> { + self.clients.values_mut() + .filter_map(|c| { + let action = c.pending_action.take()?; + Some((c.entity_id, action)) + }) + .collect() + } + + pub fn broadcast(&self, world: &World, tick: u32) { + for (addr, client) in &self.clients { + if tick % 2 != client.send_phase as u32 { continue; } + + // Step 1 — resolve player position + let Some(entity) = world.entities.get(&client.entity_id) else { continue }; + let pos = entity.pos; + let (player_cx, player_cy) = crate::world::tile_to_chunk(pos.0, pos.1); + + // Step 2 — build 9-slot chunk manifest + let mut chunks = [ChunkEntry { chunk_id: 0, version: 0 }; 9]; + for dy in -1i16..=1 { + for dx in -1i16..=1 { + let slot = ((dy + 1) * 3 + (dx + 1)) as usize; + let cx = player_cx + dx; + let cy = player_cy + dy; + chunks[slot] = ChunkEntry { + chunk_id: shared::chunk_id(cx, cy), + version: world.chunk(cx, cy).map_or(0, |c| c.version), + }; + } + } + + // Step 3 — build EntityEntry list + checksum + let viewport_entities = world.entities_in_viewport(pos); + let mut entries: Vec = viewport_entities.iter().map(|e| EntityEntry { + id: e.id, + type_id: e.type_id, + pos_x: e.pos.0, + pos_y: e.pos.1, + hp: e.hp, + hp_max: e.hp_max, + elo: 0, + entity_flags: 0, + meta_len: 0, + }).collect(); + if entries.len() > 66 { + entries.truncate(66); // TODO: multi-datagram + } + let checksum = fnv1a_entities(&entries); + + // Step 4 — send StatePacket (72 bytes) + let state = StatePacket { + header: Header::new(packet_type::STATE), + tick, + chunks, + player_entity_id: client.entity_id, + entity_checksum: checksum, + }; + let _ = self.send_tx.send((*addr, bytes_of(&state).to_vec())); + + // Step 5 — send EntityPacket (1200 bytes) + let mut pkt = ::zeroed(); + pkt.header = Header::new(packet_type::ENTITY); + pkt.tick = tick; + pkt.entity_count = entries.len() as u8; + pkt.packet_flags = 0; // no more-flag; single datagram + for (i, e) in entries.iter().enumerate() { pkt.entities[i] = *e; } + let _ = self.send_tx.send((*addr, bytes_of(&pkt).to_vec())); + + // Step 6 — send ChunkPackets for cache misses + for slot in 0..9 { + let current = chunks[slot]; + let cached = client.last_cache[slot]; + let miss = cached.chunk_id != current.chunk_id || cached.version != current.version; + if miss { + let (cx, cy) = shared::chunk_coords(current.chunk_id); + if let Some(chunk) = world.chunk(cx, cy) { + let mut cpkt = ::zeroed(); + cpkt.header = Header::new(packet_type::CHUNK); + cpkt.chunk = current; + cpkt.pal_count = chunk.pal_count; + for i in 0..64 { cpkt.palette[i] = chunk.palette[i].tile_id; } + cpkt.tiles = pack_tiles(&chunk.tiles); + let _ = self.send_tx.send((*addr, bytes_of(&cpkt).to_vec())); + } + } + } + } + } +} + +fn fnv1a_entities(entries: &[EntityEntry]) -> u32 { + const FNV_BASIS: u32 = 2_166_136_261; + const FNV_PRIME: u32 = 16_777_619; + let mut h = FNV_BASIS; + for e in entries { + for &byte in bytes_of(e) { + h ^= byte as u32; + h = h.wrapping_mul(FNV_PRIME); + } + } + h +} + +fn pack_tiles(tiles: &[u8; 1024]) -> [u8; 768] { + let mut raw = [0u8; 768]; + for i in 0..256 { + let ia = tiles[i*4] & 0x3F; + let ib = tiles[i*4+1] & 0x3F; + let ic = tiles[i*4+2] & 0x3F; + let id = tiles[i*4+3] & 0x3F; + raw[i*3] = ia | (ib << 6); + raw[i*3+1] = (ib >> 2) | (ic << 4); + raw[i*3+2] = (ic >> 4) | (id << 2); + } + raw +} diff --git a/server/src/sim.rs b/server/src/sim.rs new file mode 100755 index 0000000..f8c229c --- /dev/null +++ b/server/src/sim.rs @@ -0,0 +1,47 @@ +use std::collections::HashMap; +use shared::player_action; +use crate::world::World; + +pub struct Sim { + pub world: World, + pending: HashMap, +} + +impl Sim { + pub fn new(world: World) -> Self { + Self { world, pending: HashMap::new() } + } + + pub fn tick(&mut self, tick: u32, actions: &[(u32, u16)]) { + for &(entity_id, action) in actions { + self.pending.insert(entity_id, action); + } + if tick.is_multiple_of(4) { + let pending = std::mem::take(&mut self.pending); + self.entity_tick(&pending); + } + } + + fn entity_tick(&mut self, actions: &HashMap) { + for (&entity_id, &action) in actions { + if action == player_action::NOOP { + continue; + } + let pos = match self.world.entities.get(&entity_id) { + Some(e) => e.pos, + None => continue, + }; + let (nx, ny) = match action { + player_action::NORTH => (pos.0, pos.1 - 1), + player_action::EAST => (pos.0 + 1, pos.1), + player_action::SOUTH => (pos.0, pos.1 + 1), + player_action::WEST => (pos.0 - 1, pos.1), + _ => continue, + }; + let blocked = self.world.tile_flags(nx, ny).map_or(true, |f| f.collidable()); + if !blocked { + self.world.move_entity(entity_id, (nx, ny)); + } + } + } +} diff --git a/server/src/world.rs b/server/src/world.rs new file mode 100755 index 0000000..3929514 --- /dev/null +++ b/server/src/world.rs @@ -0,0 +1,126 @@ +use std::collections::HashMap; +use crate::chunk::{Chunk, TileDef, TileFlags, PaletteFullError}; +use crate::entity::Entity; + +pub fn tile_to_chunk(tx: i16, ty: i16) -> (i16, i16) { + (tx >> 5, ty >> 5) +} + +pub fn tile_to_local(tx: i16, ty: i16) -> (u8, u8) { + ((tx & 31) as u8, (ty & 31) as u8) +} + +pub fn local_to_tile(cx: i16, cy: i16, lx: u8, ly: u8) -> (i16, i16) { + ((cx << 5) | lx as i16, (cy << 5) | ly as i16) +} + +pub struct World { + chunks: HashMap<(i16, i16), Chunk>, + pub entities: HashMap, + chunk_entities: HashMap<(i16, i16), Vec>, + next_entity_id: u32, +} + +impl World { + pub fn new() -> Self { + Self { + chunks: HashMap::new(), + entities: HashMap::new(), + chunk_entities: HashMap::new(), + next_entity_id: 1, + } + } + + pub fn chunk(&self, cx: i16, cy: i16) -> Option<&Chunk> { + self.chunks.get(&(cx, cy)) + } + + pub fn set_chunk(&mut self, cx: i16, cy: i16, chunk: Chunk) { + self.chunks.insert((cx, cy), chunk); + } + + pub fn tile_flags(&self, wx: i16, wy: i16) -> Option { + let (cx, cy) = tile_to_chunk(wx, wy); + let (lx, ly) = tile_to_local(wx, wy); + self.chunks.get(&(cx, cy)).map(|c| c.tile_flags(lx, ly)) + } + + pub fn set_tile(&mut self, wx: i16, wy: i16, def: TileDef) -> Result<(), PaletteFullError> { + let (cx, cy) = tile_to_chunk(wx, wy); + let (lx, ly) = tile_to_local(wx, wy); + let chunk = self.chunks.entry((cx, cy)).or_insert_with(Chunk::new); + chunk.set_tile(lx, ly, def) + } + + pub fn spawn_entity(&mut self, type_id: u16, pos: (i16, i16), hp: u16) -> u32 { + let id = self.next_entity_id; + self.next_entity_id += 1; + let entity = Entity::new(id, type_id, pos, hp); + self.add_entity(entity); + id + } + + pub fn add_entity(&mut self, entity: Entity) { + let chunk_key = tile_to_chunk(entity.pos.0, entity.pos.1); + let id = entity.id; + self.entities.insert(id, entity); + self.chunk_entities.entry(chunk_key).or_default().push(id); + } + + pub fn remove_entity(&mut self, id: u32) { + if let Some(entity) = self.entities.remove(&id) { + let chunk_key = tile_to_chunk(entity.pos.0, entity.pos.1); + if let Some(list) = self.chunk_entities.get_mut(&chunk_key) { + list.retain(|&eid| eid != id); + } + } + } + + pub fn move_entity(&mut self, id: u32, new_pos: (i16, i16)) { + if let Some(entity) = self.entities.get_mut(&id) { + let old_chunk = tile_to_chunk(entity.pos.0, entity.pos.1); + let new_chunk = tile_to_chunk(new_pos.0, new_pos.1); + entity.pos = new_pos; + if old_chunk != new_chunk { + if let Some(list) = self.chunk_entities.get_mut(&old_chunk) { + list.retain(|&eid| eid != id); + } + self.chunk_entities.entry(new_chunk).or_default().push(id); + } + } + } + + pub fn entities_in_chunk(&self, cx: i16, cy: i16) -> &[u32] { + self.chunk_entities.get(&(cx, cy)).map_or(&[], Vec::as_slice) + } + + /// Returns all entities within a 30×30 tile viewport centered on `center`. + pub fn entities_in_viewport(&self, center: (i16, i16)) -> Vec<&Entity> { + let half: i16 = 15; + let min_x = center.0 - half; + let max_x = center.0 + half - 1; + let min_y = center.1 - half; + let max_y = center.1 + half - 1; + + let min_cx = tile_to_chunk(min_x, min_y).0; + let max_cx = tile_to_chunk(max_x, max_y).0; + let min_cy = tile_to_chunk(min_x, min_y).1; + let max_cy = tile_to_chunk(max_x, max_y).1; + + let mut result = Vec::new(); + for cy in min_cy..=max_cy { + for cx in min_cx..=max_cx { + for &eid in self.entities_in_chunk(cx, cy) { + if let Some(e) = self.entities.get(&eid) { + if e.pos.0 >= min_x && e.pos.0 <= max_x + && e.pos.1 >= min_y && e.pos.1 <= max_y + { + result.push(e); + } + } + } + } + } + result + } +} diff --git a/shared/Cargo.toml b/shared/Cargo.toml new file mode 100755 index 0000000..603fdb4 --- /dev/null +++ b/shared/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "shared" +version = "0.1.0" +edition = "2024" + +[dependencies] +bytemuck = { version = "1.24.0", features = ["derive", "min_const_generics"] } diff --git a/shared/src/lib.rs b/shared/src/lib.rs new file mode 100755 index 0000000..29f2aba --- /dev/null +++ b/shared/src/lib.rs @@ -0,0 +1,136 @@ +use bytemuck::{Pod, Zeroable}; + +pub mod player_action { + pub const NOOP: u16 = 0; + pub const NORTH: u16 = 1; + pub const EAST: u16 = 2; + pub const SOUTH: u16 = 3; + pub const WEST: u16 = 4; +} + +pub const MAGIC: u16 = 0x524C; +pub const VERSION: u8 = 1; + +pub mod packet_type { + pub const STATE: u8 = 0; + pub const ACTION: u8 = 1; + pub const CHUNK: u8 = 2; + pub const ENTITY: u8 = 3; + pub const ENTITY_QUERY: u8 = 4; + pub const ENTITY_DETAIL: u8 = 5; + pub const PING: u8 = 6; + pub const PONG: u8 = 7; +} + +/// Encodes signed chunk grid coordinates into the wire chunk_id format. +pub fn chunk_id(cx: i16, cy: i16) -> u32 { + (cx as u16 as u32) | ((cy as u16 as u32) << 16) +} + +/// Decodes a wire chunk_id back to signed chunk grid coordinates. +pub fn chunk_coords(id: u32) -> (i16, i16) { + (id as u16 as i16, (id >> 16) as u16 as i16) +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +pub struct Header { + pub magic: u16, + pub version: u8, + pub packet_type: u8, + pub client_type: u16, +} + +impl Header { + pub fn new(packet_type: u8) -> Self { + Self { magic: MAGIC, version: VERSION, packet_type, client_type: 0 } + } +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +pub struct ChunkEntry { + pub chunk_id: u32, + pub version: u16, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +pub struct ActionPacket { + pub header: Header, + pub auth_token: u64, + pub sequence: u32, + /// Slot index = (dy+1)*3 + (dx+1), dx/dy ∈ {-1, 0, 1}. + /// Slot 4 is always the player's current chunk. + pub cache: [ChunkEntry; 9], + pub player_action: u16, +} + +const _: () = assert!(std::mem::size_of::() == 6); +const _: () = assert!(std::mem::size_of::() == 74); + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +pub struct StatePacket { + pub header: Header, // 6 (offset 0) + pub tick: u32, // 4 (offset 6) + /// Slot index = (dy+1)*3 + (dx+1), dx/dy ∈ {-1, 0, 1}. + /// Slot 4 is always the player's current chunk. + pub chunks: [ChunkEntry; 9], // 54 (offset 10) + pub player_entity_id: u32, // 4 (offset 64) + pub entity_checksum: u32, // 4 (offset 68) +} // total: 72 bytes + +const _: () = assert!(std::mem::size_of::() == 72); + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +pub struct PingPacket { + pub header: Header, + pub timestamp_ms: u64, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +pub struct PongPacket { + pub header: Header, + pub timestamp_ms: u64, +} + +#[derive(Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +pub struct ChunkPacket { + pub header: Header, // 6 bytes + pub chunk: ChunkEntry, // 6 bytes + pub pal_count: u8, // 1 byte + pub palette: [u16; 64], // 128 bytes + pub tiles: [u8; 768], // 768 bytes — 1024 tiles, 6-bit packed +} // total: 909 bytes + +const _: () = assert!(std::mem::size_of::() == 909); + +#[repr(C, packed)] +#[derive(Clone, Copy, Pod, Zeroable)] +pub struct EntityEntry { + pub id: u32, + pub type_id: u16, + pub pos_x: i16, + pub pos_y: i16, + pub hp: u16, + pub hp_max: u16, + pub elo: u16, + pub entity_flags: u8, + pub meta_len: u8, +} +const _: () = assert!(std::mem::size_of::() == 18); + +#[repr(C, packed)] +#[derive(Clone, Copy, Pod, Zeroable)] +pub struct EntityPacket { + pub header: Header, + pub tick: u32, + pub entity_count: u8, + pub packet_flags: u8, + pub entities: [EntityEntry; 66], +} +const _: () = assert!(std::mem::size_of::() == 1200);