Week 09 of 2024

Development log of Otterhide

32 items
  1. Setup a development environment and CI / CD
  2. Ensure the assets/ directory exists
  3. Setup a Bevy application
  4. Make the canvas fill the viewport
  5. Setup a ground plane and a camera
  6. Setup a house (cuboid) and sunlight
  7. Set the camera in motion
  8. Remove duplicated web/develop goal from the Makefile
  9. Use a gltf model for a house and a sculpture
  10. Update some Cargo dependencies
  11. Implement basic logic concerning day-month
  12. Let the time pass but keep an eye on it
  13. Slow down the camera
  14. Simulate a horizon
  15. Move the camera related logic to own module
  16. Parameterize the camera plugin
  17. Implement day-night cycle with sunlight
  18. Format some code
  19. Separate date logic to a new plugin
  20. Make the ground material (grass) more rough
  21. Implement buildings plugin
  22. Let the houses stand in rows of 6
  23. Prevent negative luminosity at night
  24. Prevent initial flash of white before game loads
  25. Separate ground logic to a plugin
  26. Code consistency: use unqualified SunPlugin
  27. Introduce game states: Simulate and Explore
  28. Decouple construction orders from construction
  29. Update dependencies (Nix and Cargo)
  30. The date progression will stop as the sim is done
  31. Implement POC snapshots and rollback (time travel)
  32. Set simulation to 15 years

Setup a development environment and CI / CD

On by Tad Lispy

It's adapted from https://gitlab.com/tad-lispy/bevy-lion-playground, but all the code is removed. There are some dependencies in Cargo.toml that are not yet used, but I expect to use them later.

new file mode 100644
index 0000000..5330b9c
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,28 @@
+shopt -s globstar
+
+watch_file **/*.nix
+watch_file flake.lock
+watch_file rust-toolchain.toml
+
+use_flake() {
+    mkdir -p "$(direnv_layout_dir)"
+    eval "$(nix print-dev-env --profile "$(direnv_layout_dir)/flake-profile")"
+}
+
+if (nix help flake &> /dev/null)
+then
+    # Nix with flakes support
+    use flake
+
+elif command -v lorri
+then
+    # Lorri is installed
+    eval "$(lorri direnv)"
+
+else
+    # Standard nix - going to be slooow
+    use nix
+
+fi
+
+source_env_if_exists .envrc.private
new file mode 100644
index 0000000..e426355
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.direnv/
+/target/
+/result
+/public/
+/dist/
+/web/
+/build-image
+/.cargo-home/
new file mode 100644
index 0000000..76bc656
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,88 @@
+variables:
+  project_name: "otterhide" # This has to align with flake.nix
+
+test:
+  stage: test
+  image: nixpkgs/nix-flakes:nixos-23.11
+
+  script:
+    - nix develop --command make test
+
+  artifacts:
+    name: "$CI_COMMIT_REF_SLUG"
+    expire_in: 1 week
+    paths:
+      - dist/
+
+  except:
+    - main
+
+  cache:
+    key: shared-cache
+    paths:
+      - target/
+
+pages:
+  stage: deploy
+  image: $CI_REGISTRY_IMAGE:build
+  variables:
+    CARGO_HOME: ${CI_PROJECT_DIR}/.cargo-home/
+  script:
+    - base_url="/$(cut --delimiter='/' --fields=4- <<< $CI_PAGES_URL)"
+    - make public base_url=${base_url}
+
+  artifacts:
+    paths:
+      - public/
+
+  only:
+    - main
+
+  cache:
+    key: cargo
+    paths:
+      - ${CARGO_HOME}/bin/
+      - ${CARGO_HOME}/registry/index/
+      - ${CARGO_HOME}/registry/cache/
+      - ${CARGO_HOME}/git/db/
+      - target/
+
+
+build-image:
+  stage: build
+  image: nixpkgs/nix-flakes:nixos-23.11
+  script:
+    - nix run nixpkgs#gnumake -- build-image
+  artifacts:
+    paths:
+      - build-image
+  cache:
+    key: build-image
+    paths:
+      - build-image
+  rules:
+    - changes:
+        - flake.nix
+        - flake.lock
+        - rust-toolchain.toml
+        - .gitlab-ci.yml
+
+
+publish-build-image:
+  stage: build
+  needs:
+    - build-image
+  dependencies:
+    - build-image
+  image: quay.io/podman/stable
+  script:
+    - gzip --decompress --to-stdout build-image | podman image load
+    - podman image tag ${project_name}-build-image $CI_REGISTRY_IMAGE:build
+    - podman login --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASSWORD $CI_REGISTRY
+    - podman image push $CI_REGISTRY_IMAGE:build
+  rules:
+    - changes:
+        - flake.nix
+        - flake.lock
+        - rust-toolchain.toml
+        - .gitlab-ci.yml
new file mode 100644
index 0000000..8bb2117
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,4333 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ab_glyph"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225"
+dependencies = [
+ "ab_glyph_rasterizer",
+ "owned_ttf_parser",
+]
+
+[[package]]
+name = "ab_glyph_rasterizer"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
+
+[[package]]
+name = "accesskit"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb10ed32c63247e4e39a8f42e8e30fb9442fbf7878c8e4a9849e7e381619bea"
+
+[[package]]
+name = "accesskit_consumer"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6"
+dependencies = [
+ "accesskit",
+]
+
+[[package]]
+name = "accesskit_macos"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer",
+ "objc2 0.3.0-beta.3.patch-leaks.3",
+ "once_cell",
+]
+
+[[package]]
+name = "accesskit_windows"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer",
+ "once_cell",
+ "paste",
+ "static_assertions",
+ "windows 0.48.0",
+]
+
+[[package]]
+name = "accesskit_winit"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45f8f7c9f66d454d5fd8e344c8c8c7324b57194e1041b955519fc58a01e77a25"
+dependencies = [
+ "accesskit",
+ "accesskit_macos",
+ "accesskit_windows",
+ "raw-window-handle 0.6.0",
+ "winit",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+
+[[package]]
+name = "alsa"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47"
+dependencies = [
+ "alsa-sys",
+ "bitflags 1.3.2",
+ "libc",
+ "nix 0.24.3",
+]
+
+[[package]]
+name = "alsa-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "android-activity"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289"
+dependencies = [
+ "android-properties",
+ "bitflags 2.4.2",
+ "cc",
+ "cesu8",
+ "jni 0.21.1",
+ "jni-sys",
+ "libc",
+ "log",
+ "ndk 0.8.0",
+ "ndk-context",
+ "ndk-sys 0.5.0+25.2.9519653",
+ "num_enum 0.7.2",
+ "thiserror",
+]
+
+[[package]]
+name = "android-properties"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
+
+[[package]]
+name = "android_log-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
+
+[[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 = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[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.37.3+1.3.251"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
+dependencies = [
+ "libloading 0.7.4",
+]
+
+[[package]]
+name = "async-broadcast"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
+dependencies = [
+ "event-listener 2.5.3",
+ "futures-core",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
+dependencies = [
+ "concurrent-queue",
+ "event-listener 5.1.0",
+ "event-listener-strategy 0.5.0",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
+dependencies = [
+ "async-lock",
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc19683171f287921f2405677dd2ed2549c3b3bda697a563ebc3a121ace2aba1"
+dependencies = [
+ "async-lock",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
+dependencies = [
+ "event-listener 4.0.3",
+ "event-listener-strategy 0.4.0",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
+
+[[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.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "bevy"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "611dd99f412e862610adb43e2243b16436c6d8009f6d9dbe8ce3d6d840b34029"
+dependencies = [
+ "bevy_internal",
+]
+
+[[package]]
+name = "bevy_a11y"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bf80cd6d0dca4073f9b34b16f1d187a4caa035fd841892519431783bbc9e287"
+dependencies = [
+ "accesskit",
+ "bevy_app",
+ "bevy_derive",
+ "bevy_ecs",
+]
+
+[[package]]
+name = "bevy_animation"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa4ef4c35533df3f0c4e938cf6a831456ea563775bab799336f74331140c7665"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core",
+ "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_time",
+ "bevy_transform",
+ "bevy_utils",
+]
+
+[[package]]
+name = "bevy_app"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bce3544afc010ffed39c136f6d5a9322d20d38df1394d468ba9106caa0434cb"
+dependencies = [
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_reflect",
+ "bevy_tasks",
+ "bevy_utils",
+ "downcast-rs",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "bevy_asset"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac185d8e29c7eb0194f8aae7af3f7234f7ca7a448293be1d3d0d8fef435f65ec"
+dependencies = [
+ "async-broadcast",
+ "async-fs",
+ "async-lock",
+ "bevy_app",
+ "bevy_asset_macros",
+ "bevy_ecs",
+ "bevy_log",
+ "bevy_reflect",
+ "bevy_tasks",
+ "bevy_utils",
+ "bevy_winit",
+ "blake3",
+ "crossbeam-channel",
+ "downcast-rs",
+ "futures-io",
+ "futures-lite",
+ "js-sys",
+ "parking_lot",
+ "ron",
+ "serde",
+ "thiserror",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "bevy_asset_macros"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb82d1aac8251378c45a8d0ad788d1bf75f54db28c1750f84f1fd7c00127927a"
+dependencies = [
+ "bevy_macro_utils",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "bevy_audio"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4fe7f952e5e0a343fbde43180db7b8e719ad78594480c91b26876623944a3a1"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_transform",
+ "bevy_utils",
+ "oboe",
+ "rodio",
+]
+
+[[package]]
+name = "bevy_core"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7b1b340b8d08f48ecd51b97589d261f5023a7b073d25e300382c49146524103"
+dependencies = [
+ "bevy_app",
+ "bevy_ecs",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_tasks",
+ "bevy_utils",
+ "bytemuck",
+]
+
+[[package]]
+name = "bevy_core_pipeline"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626a5aaadbdd69eae020c5856575d2d0113423ae1ae1351377e20956d940052c"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_log",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_transform",
+ "bevy_utils",
+ "bitflags 2.4.2",
+ "radsort",
+ "serde",
+]
+
+[[package]]
+name = "bevy_derive"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "028ae2a34678055185d7f1beebb1ebe6a8dcf3733e139e4ee1383a7f29ae8ba6"
+dependencies = [
+ "bevy_macro_utils",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "bevy_diagnostic"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01a104acfdc5280accd01a3524810daf3bda72924e3da0c8a9ec816a57eef4e3"
+dependencies = [
+ "bevy_app",
+ "bevy_core",
+ "bevy_ecs",
+ "bevy_log",
+ "bevy_time",
+ "bevy_utils",
+ "const-fnv1a-hash",
+ "sysinfo",
+]
+
+[[package]]
+name = "bevy_ecs"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b85406d5febbbdbcac4444ef61cd9a816f2f025ed692a3fc5439a32153070304"
+dependencies = [
+ "async-channel",
+ "bevy_ecs_macros",
+ "bevy_ptr",
+ "bevy_reflect",
+ "bevy_tasks",
+ "bevy_utils",
+ "downcast-rs",
+ "fixedbitset",
+ "rustc-hash",
+ "serde",
+ "thiserror",
+ "thread_local",
+]
+
+[[package]]
+name = "bevy_ecs_macros"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3ce4b65d7c5f1990e729df75cec2ea6e2241b4a0c37b31c281a04c59c11b7b"
+dependencies = [
+ "bevy_macro_utils",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "bevy_encase_derive"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c3d301922e76b16819e17c8cc43b34e92c13ccd06ad19dfa3e52a91a0e13e5c"
+dependencies = [
+ "bevy_macro_utils",
+ "encase_derive_impl",
+]
+
+[[package]]
+name = "bevy_gilrs"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96364a1875ee4545fcf825c78dc065ddb9a3b2a509083ef11142f9de0eb8aa17"
+dependencies = [
+ "bevy_app",
+ "bevy_ecs",
+ "bevy_input",
+ "bevy_log",
+ "bevy_time",
+ "bevy_utils",
+ "gilrs",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_gizmos"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdca80b7b4db340eb666d69374a0195b3935759120d0b990fcef8b27d0fb3680"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core",
+ "bevy_core_pipeline",
+ "bevy_ecs",
+ "bevy_gizmos_macros",
+ "bevy_log",
+ "bevy_math",
+ "bevy_pbr",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_sprite",
+ "bevy_transform",
+ "bevy_utils",
+]
+
+[[package]]
+name = "bevy_gizmos_macros"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a949eb8b4538a6e4d875321cda2b63dc0fb0317cf18c8245ca5a32f24f6d26d"
+dependencies = [
+ "bevy_macro_utils",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "bevy_gltf"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031d0c2a7c0353bb9ac08a5130e58b9a2de3cdaa3c31b5da00b22a9e4732a155"
+dependencies = [
+ "base64",
+ "bevy_animation",
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core",
+ "bevy_core_pipeline",
+ "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_log",
+ "bevy_math",
+ "bevy_pbr",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_scene",
+ "bevy_tasks",
+ "bevy_transform",
+ "bevy_utils",
+ "gltf",
+ "percent-encoding",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_hierarchy"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9f9f843e43d921f07658c24eae74285efc7a335c87998596f3f365155320c69"
+dependencies = [
+ "bevy_app",
+ "bevy_core",
+ "bevy_ecs",
+ "bevy_log",
+ "bevy_reflect",
+ "bevy_utils",
+]
+
+[[package]]
+name = "bevy_input"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9cb5b2f3747ffb00cf7e3d6b52f7384476921cd31f0cfd3d1ddff31f83d9252"
+dependencies = [
+ "bevy_app",
+ "bevy_ecs",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_utils",
+ "smol_str",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_internal"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7af89c7083830b1d65fcf0260c3d2537c397fe8ce871471b6e97198a4704f23e"
+dependencies = [
+ "bevy_a11y",
+ "bevy_animation",
+ "bevy_app",
+ "bevy_asset",
+ "bevy_audio",
+ "bevy_core",
+ "bevy_core_pipeline",
+ "bevy_derive",
+ "bevy_diagnostic",
+ "bevy_ecs",
+ "bevy_gilrs",
+ "bevy_gizmos",
+ "bevy_gltf",
+ "bevy_hierarchy",
+ "bevy_input",
+ "bevy_log",
+ "bevy_math",
+ "bevy_pbr",
+ "bevy_ptr",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_scene",
+ "bevy_sprite",
+ "bevy_tasks",
+ "bevy_text",
+ "bevy_time",
+ "bevy_transform",
+ "bevy_ui",
+ "bevy_utils",
+ "bevy_window",
+ "bevy_winit",
+]
+
+[[package]]
+name = "bevy_log"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfd5bcc3531f8008897fb03cc8751b86d0d29ef94f8fd38b422f9603b7ae80d0"
+dependencies = [
+ "android_log-sys",
+ "bevy_app",
+ "bevy_ecs",
+ "bevy_utils",
+ "console_error_panic_hook",
+ "tracing-log 0.1.4",
+ "tracing-subscriber",
+ "tracing-wasm",
+]
+
+[[package]]
+name = "bevy_macro_utils"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac4401c25b197e7c1455a4875a90b61bba047a9e8d290ce029082c818ab1a21c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc-hash",
+ "syn 2.0.50",
+ "toml_edit 0.21.1",
+]
+
+[[package]]
+name = "bevy_math"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f312b1b8aa6d3965b65040b08e33efac030db3071f20b44f9da9c4c3dfcaf76"
+dependencies = [
+ "glam",
+ "serde",
+]
+
+[[package]]
+name = "bevy_mikktspace"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3075c01f2b1799945892d5310fc1836e47c045dfe6af5878a304a475931a0c5f"
+dependencies = [
+ "glam",
+]
+
+[[package]]
+name = "bevy_pbr"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31c72bf12e50ff76c9ed9a7c51ceb88bfea9865d00f24d95b12344fffe1e270"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core_pipeline",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_transform",
+ "bevy_utils",
+ "bevy_window",
+ "bitflags 2.4.2",
+ "bytemuck",
+ "fixedbitset",
+ "radsort",
+ "smallvec",
+ "thread_local",
+]
+
+[[package]]
+name = "bevy_ptr"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86afa4a88ee06b10fe1e6f28a796ba2eedd16804717cbbb911df0cbb0cd6677b"
+
+[[package]]
+name = "bevy_reflect"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "133dfab8d403d0575eeed9084e85780bbb449dcf75dd687448439117789b40a2"
+dependencies = [
+ "bevy_math",
+ "bevy_ptr",
+ "bevy_reflect_derive",
+ "bevy_utils",
+ "downcast-rs",
+ "erased-serde",
+ "glam",
+ "serde",
+ "smol_str",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_reflect_derive"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce1679a4dfdb2c9ff24ca590914c3cec119d7c9e1b56fa637776913acc030386"
+dependencies = [
+ "bevy_macro_utils",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+ "uuid",
+]
+
+[[package]]
+name = "bevy_render"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3b194b7029b7541ef9206ac3cb696d3cb37f70bd3260d293fc00d378547e892"
+dependencies = [
+ "async-channel",
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_encase_derive",
+ "bevy_hierarchy",
+ "bevy_log",
+ "bevy_math",
+ "bevy_mikktspace",
+ "bevy_reflect",
+ "bevy_render_macros",
+ "bevy_tasks",
+ "bevy_time",
+ "bevy_transform",
+ "bevy_utils",
+ "bevy_window",
+ "bitflags 2.4.2",
+ "bytemuck",
+ "codespan-reporting",
+ "downcast-rs",
+ "encase",
+ "futures-lite",
+ "hexasphere",
+ "image",
+ "js-sys",
+ "ktx2",
+ "naga",
+ "naga_oil",
+ "ruzstd",
+ "serde",
+ "thiserror",
+ "thread_local",
+ "wasm-bindgen",
+ "web-sys",
+ "wgpu",
+]
+
+[[package]]
+name = "bevy_render_macros"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4aa6d99b50375bb7f63be2c3055dfe2f926f7b3c4db108bb0b1181b4f02766aa"
+dependencies = [
+ "bevy_macro_utils",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "bevy_scene"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c3c82eaff0b22949183a75a7e2d7fc4ece808235918b34c5b282aab52c3563a"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_transform",
+ "bevy_utils",
+ "serde",
+ "thiserror",
+ "uuid",
+]
+
+[[package]]
+name = "bevy_sprite"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea977d7d7c48fc4ba283d449f09528c4e70db17c9048e32e99ecd9890d72223"
+dependencies = [
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core_pipeline",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_log",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_transform",
+ "bevy_utils",
+ "bitflags 2.4.2",
+ "bytemuck",
+ "fixedbitset",
+ "guillotiere",
+ "radsort",
+ "rectangle-pack",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_tasks"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b20f243f6fc4c4ba10c2dbff891e947ddae947bb20b263f43e023558b35294bd"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-task",
+ "concurrent-queue",
+ "futures-lite",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "bevy_text"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "006990d27551dbc339774178e833290952511621662fd5ca23a4e6e922ab2d9f"
+dependencies = [
+ "ab_glyph",
+ "bevy_app",
+ "bevy_asset",
+ "bevy_ecs",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_sprite",
+ "bevy_transform",
+ "bevy_utils",
+ "bevy_window",
+ "glyph_brush_layout",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_time"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9738901b6b251d2c9250542af7002d6f671401fc3b74504682697c5ec822f210"
+dependencies = [
+ "bevy_app",
+ "bevy_ecs",
+ "bevy_reflect",
+ "bevy_utils",
+ "crossbeam-channel",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_transform"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73744a95bc4b8683e91cea3e79b1ad0844c1d677f31fbbc1814c79a5b4f8f0"
+dependencies = [
+ "bevy_app",
+ "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_math",
+ "bevy_reflect",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_ui"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fafe872906bac6d7fc8ecff166f56b4253465b2895ed88801499aa113548ccc6"
+dependencies = [
+ "bevy_a11y",
+ "bevy_app",
+ "bevy_asset",
+ "bevy_core_pipeline",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_input",
+ "bevy_log",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_render",
+ "bevy_sprite",
+ "bevy_text",
+ "bevy_transform",
+ "bevy_utils",
+ "bevy_window",
+ "bytemuck",
+ "taffy",
+ "thiserror",
+]
+
+[[package]]
+name = "bevy_utils"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94a06aca1c1863606416b892f4c79e300dbc6211b6690953269051a431c2cca0"
+dependencies = [
+ "ahash",
+ "bevy_utils_proc_macros",
+ "getrandom",
+ "hashbrown",
+ "nonmax",
+ "petgraph",
+ "smallvec",
+ "thiserror",
+ "tracing",
+ "uuid",
+ "web-time",
+]
+
+[[package]]
+name = "bevy_utils_proc_macros"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31ae98e9c0c08b0f5c90e22cd713201f759b98d4fd570b99867a695f8641859a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "bevy_window"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb627efd7622a61398ac0d3674f93c997cffe16f13c59fb8ae8a05c9e28de961"
+dependencies = [
+ "bevy_a11y",
+ "bevy_app",
+ "bevy_ecs",
+ "bevy_input",
+ "bevy_math",
+ "bevy_reflect",
+ "bevy_utils",
+ "raw-window-handle 0.6.0",
+ "smol_str",
+]
+
+[[package]]
+name = "bevy_winit"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55105324a201941ae587790f83f6d9caa327e0baa0205558ec41e5ee05a1f703"
+dependencies = [
+ "accesskit_winit",
+ "approx",
+ "bevy_a11y",
+ "bevy_app",
+ "bevy_derive",
+ "bevy_ecs",
+ "bevy_hierarchy",
+ "bevy_input",
+ "bevy_math",
+ "bevy_tasks",
+ "bevy_utils",
+ "bevy_window",
+ "crossbeam-channel",
+ "raw-window-handle 0.6.0",
+ "wasm-bindgen",
+ "web-sys",
+ "winit",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.69.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
+dependencies = [
+ "bitflags 2.4.2",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "lazy_static",
+ "lazycell",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "blake3"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-sys"
+version = "0.1.0-beta.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146"
+dependencies = [
+ "objc-sys 0.2.0-beta.2",
+]
+
+[[package]]
+name = "block-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7"
+dependencies = [
+ "objc-sys 0.3.2",
+]
+
+[[package]]
+name = "block2"
+version = "0.2.0-alpha.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42"
+dependencies = [
+ "block-sys 0.1.0-beta.1",
+ "objc2-encode 2.0.0-pre.2",
+]
+
+[[package]]
+name = "block2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68"
+dependencies = [
+ "block-sys 0.2.1",
+ "objc2 0.4.1",
+]
+
+[[package]]
+name = "blocking"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
+dependencies = [
+ "async-channel",
+ "async-lock",
+ "async-task",
+ "fastrand",
+ "futures-io",
+ "futures-lite",
+ "piper",
+ "tracing",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
+
+[[package]]
+name = "bytemuck"
+version = "1.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+
+[[package]]
+name = "calloop"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298"
+dependencies = [
+ "bitflags 2.4.2",
+ "log",
+ "polling",
+ "rustix",
+ "slab",
+ "thiserror",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
+dependencies = [
+ "calloop",
+ "rustix",
+ "wayland-backend",
+ "wayland-client",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
+[[package]]
+name = "clang-sys"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading 0.8.1",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "com"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6"
+dependencies = [
+ "com_macros",
+]
+
+[[package]]
+name = "com_macros"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5"
+dependencies = [
+ "com_macros_support",
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "com_macros_support"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "const-fnv1a-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca"
+
+[[package]]
+name = "const_panic"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b"
+
+[[package]]
+name = "const_soft_float"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
+
+[[package]]
+name = "constgebra"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd23e864550e6dafc1e41ac78ce4f1ccddc8672b40c403524a04ff3f0518420"
+dependencies = [
+ "const_soft_float",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[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-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "core-graphics"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "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",
+ "libc",
+]
+
+[[package]]
+name = "coreaudio-rs"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation-sys",
+ "coreaudio-sys",
+]
+
+[[package]]
+name = "coreaudio-sys"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9"
+dependencies = [
+ "bindgen",
+]
+
+[[package]]
+name = "cpal"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c"
+dependencies = [
+ "alsa",
+ "core-foundation-sys",
+ "coreaudio-rs",
+ "dasp_sample",
+ "jni 0.19.0",
+ "js-sys",
+ "libc",
+ "mach2",
+ "ndk 0.7.0",
+ "ndk-context",
+ "oboe",
+ "once_cell",
+ "parking_lot",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows 0.46.0",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "cursor-icon"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
+
+[[package]]
+name = "d3d12"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307"
+dependencies = [
+ "bitflags 2.4.2",
+ "libloading 0.8.1",
+ "winapi",
+]
+
+[[package]]
+name = "dasp_sample"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
+
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 1.0.109",
+]
+
+[[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.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading 0.8.1",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
+
+[[package]]
+name = "either"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
+
+[[package]]
+name = "encase"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ed933078d2e659745df651f4c180511cd582e5b9414ff896e7d50d207e3103"
+dependencies = [
+ "const_panic",
+ "encase_derive",
+ "glam",
+ "thiserror",
+]
+
+[[package]]
+name = "encase_derive"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4ce1449c7d19eba6cc0abd231150ad81620a8dce29601d7f8d236e5d431d72a"
+dependencies = [
+ "encase_derive_impl",
+]
+
+[[package]]
+name = "encase_derive_impl"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92959a9e8d13eaa13b8ae8c7b583c3bf1669ca7a8e7708a088d12587ba86effc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "erased-serde"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388979d208a049ffdfb22fa33b9c81942215b940910bccfe258caeb25d125cb3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "euclid"
+version = "0.22.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "4.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
+dependencies = [
+ "event-listener 4.0.3",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
+dependencies = [
+ "event-listener 5.1.0",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
+name = "fdeflate"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-lite"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "gethostname"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
+dependencies = [
+ "libc",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gilrs"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b2e57a9cb946b5d04ae8638c5f554abb5a9f82c4c950fd5b1fee6d119592fb"
+dependencies = [
+ "fnv",
+ "gilrs-core",
+ "log",
+ "uuid",
+ "vec_map",
+]
+
+[[package]]
+name = "gilrs-core"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0af1827b7dd2f36d740ae804c1b3ea0d64c12533fb61ff91883005143a0e8c5a"
+dependencies = [
+ "core-foundation",
+ "inotify",
+ "io-kit-sys",
+ "js-sys",
+ "libc",
+ "libudev-sys",
+ "log",
+ "nix 0.27.1",
+ "uuid",
+ "vec_map",
+ "wasm-bindgen",
+ "web-sys",
+ "windows 0.52.0",
+]
+
+[[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 = "glam"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
+dependencies = [
+ "bytemuck",
+ "serde",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "glow"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"
+dependencies = [
+ "js-sys",
+ "slotmap",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gltf"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b78f069cf941075835822953c345b9e1edd67ae347b81ace3aea9de38c2ef33"
+dependencies = [
+ "byteorder",
+ "gltf-json",
+ "lazy_static",
+ "serde_json",
+]
+
+[[package]]
+name = "gltf-derive"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "438ffe1a5540d75403feaf23636b164e816e93f6f03131674722b3886ce32a57"
+dependencies = [
+ "inflections",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "gltf-json"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "655951ba557f2bc69ea4b0799446bae281fa78efae6319968bdd2c3e9a06d8e1"
+dependencies = [
+ "gltf-derive",
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "glutin_wgl_sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead"
+dependencies = [
+ "gl_generator",
+]
+
+[[package]]
+name = "glyph_brush_layout"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc32c2334f00ca5ac3695c5009ae35da21da8c62d255b5b96d56e2597a637a38"
+dependencies = [
+ "ab_glyph",
+ "approx",
+ "xi-unicode",
+]
+
+[[package]]
+name = "gpu-alloc"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
+dependencies = [
+ "bitflags 2.4.2",
+ "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.4.2",
+]
+
+[[package]]
+name = "gpu-allocator"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884"
+dependencies = [
+ "log",
+ "presser",
+ "thiserror",
+ "winapi",
+ "windows 0.52.0",
+]
+
+[[package]]
+name = "gpu-descriptor"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c"
+dependencies = [
+ "bitflags 2.4.2",
+ "gpu-descriptor-types",
+ "hashbrown",
+]
+
+[[package]]
+name = "gpu-descriptor-types"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c"
+dependencies = [
+ "bitflags 2.4.2",
+]
+
+[[package]]
+name = "grid"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eec1c01eb1de97451ee0d60de7d81cf1e72aabefb021616027f3d1c3ec1c723c"
+
+[[package]]
+name = "guillotiere"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782"
+dependencies = [
+ "euclid",
+ "svg_fmt",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+ "serde",
+]
+
+[[package]]
+name = "hassle-rs"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890"
+dependencies = [
+ "bitflags 2.4.2",
+ "com",
+ "libc",
+ "libloading 0.8.1",
+ "thiserror",
+ "widestring",
+ "winapi",
+]
+
+[[package]]
+name = "hexasphere"
+version = "10.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f33ddb7f7143d9e703c072e88b98cd8b9719f174137a671429351bd2ee43c02a"
+dependencies = [
+ "constgebra",
+ "glam",
+]
+
+[[package]]
+name = "hexf-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
+
+[[package]]
+name = "icrate"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319"
+dependencies = [
+ "block2 0.3.0",
+ "dispatch",
+ "objc2 0.4.1",
+]
+
+[[package]]
+name = "image"
+version = "0.24.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "num-traits",
+ "png",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "inflections"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
+
+[[package]]
+name = "inotify"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
+dependencies = [
+ "bitflags 1.3.2",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "io-kit-sys"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4769cb30e5dcf1710fc6730d3e94f78c47723a014a567de385e113c737394640"
+dependencies = [
+ "core-foundation-sys",
+ "mach2",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "jni"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "js-sys"
+version = "0.3.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
+dependencies = [
+ "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 0.8.1",
+ "pkg-config",
+]
+
+[[package]]
+name = "khronos_api"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+
+[[package]]
+name = "ktx2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87d65e08a9ec02e409d27a0139eaa6b9756b4d81fe7cde71f6941a83730ce838"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "lewton"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030"
+dependencies = [
+ "byteorder",
+ "ogg",
+ "tinyvec",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "libloading"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "libredox"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
+dependencies = [
+ "bitflags 2.4.2",
+ "libc",
+ "redox_syscall 0.4.1",
+]
+
+[[package]]
+name = "libudev-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "mach2"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "memmap2"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "metal"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25"
+dependencies = [
+ "bitflags 2.4.2",
+ "block",
+ "core-graphics-types",
+ "foreign-types",
+ "log",
+ "objc",
+ "paste",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+dependencies = [
+ "adler",
+ "simd-adler32",
+]
+
+[[package]]
+name = "naga"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8878eb410fc90853da3908aebfe61d73d26d4437ef850b70050461f939509899"
+dependencies = [
+ "bit-set",
+ "bitflags 2.4.2",
+ "codespan-reporting",
+ "hexf-parse",
+ "indexmap",
+ "log",
+ "num-traits",
+ "pp-rs",
+ "rustc-hash",
+ "spirv",
+ "termcolor",
+ "thiserror",
+ "unicode-xid",
+]
+
+[[package]]
+name = "naga_oil"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ea62ae0f2787456afca7209ca180522b41f00cbe159ee369eba1e07d365cd1"
+dependencies = [
+ "bit-set",
+ "codespan-reporting",
+ "data-encoding",
+ "indexmap",
+ "naga",
+ "once_cell",
+ "regex",
+ "regex-syntax 0.8.2",
+ "rustc-hash",
+ "thiserror",
+ "tracing",
+ "unicode-ident",
+]
+
+[[package]]
+name = "ndk"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
+dependencies = [
+ "bitflags 1.3.2",
+ "jni-sys",
+ "ndk-sys 0.4.1+23.1.7779620",
+ "num_enum 0.5.11",
+ "raw-window-handle 0.5.2",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
+dependencies = [
+ "bitflags 2.4.2",
+ "jni-sys",
+ "log",
+ "ndk-sys 0.5.0+25.2.9519653",
+ "num_enum 0.7.2",
+ "raw-window-handle 0.6.0",
+ "thiserror",
+]
+
+[[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.4.1+23.1.7779620"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "ndk-sys"
+version = "0.5.0+25.2.9519653"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "nix"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "nix"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+dependencies = [
+ "bitflags 2.4.2",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nonmax"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51"
+
+[[package]]
+name = "ntapi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
+dependencies = [
+ "num_enum_derive 0.5.11",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
+dependencies = [
+ "num_enum_derive 0.7.2",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
+dependencies = [
+ "proc-macro-crate 3.1.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+ "objc_exception",
+]
+
+[[package]]
+name = "objc-sys"
+version = "0.2.0-beta.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7"
+
+[[package]]
+name = "objc-sys"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459"
+
+[[package]]
+name = "objc2"
+version = "0.3.0-beta.3.patch-leaks.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468"
+dependencies = [
+ "block2 0.2.0-alpha.6",
+ "objc-sys 0.2.0-beta.2",
+ "objc2-encode 2.0.0-pre.2",
+]
+
+[[package]]
+name = "objc2"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d"
+dependencies = [
+ "objc-sys 0.3.2",
+ "objc2-encode 3.0.0",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "2.0.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512"
+dependencies = [
+ "objc-sys 0.2.0-beta.2",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666"
+
+[[package]]
+name = "objc_exception"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "oboe"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0"
+dependencies = [
+ "jni 0.20.0",
+ "ndk 0.7.0",
+ "ndk-context",
+ "num-derive",
+ "num-traits",
+ "oboe-sys",
+]
+
+[[package]]
+name = "oboe-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ogg"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "orbclient"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166"
+dependencies = [
+ "libredox",
+]
+
+[[package]]
+name = "otterhide"
+version = "0.1.0"
+dependencies = [
+ "bevy",
+ "derive_more",
+ "itertools",
+ "js-sys",
+ "rand",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "owned_ttf_parser"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7"
+dependencies = [
+ "ttf-parser",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.4.1",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "petgraph"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "piper"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "png"
+version = "0.17.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "polling"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite",
+ "rustix",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "pp-rs"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[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 = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
+dependencies = [
+ "toml_edit 0.21.1",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "profiling"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
+
+[[package]]
+name = "quick-xml"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radsort"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "range-alloc"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
+
+[[package]]
+name = "raw-window-handle"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
+
+[[package]]
+name = "raw-window-handle"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
+
+[[package]]
+name = "rectangle-pack"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb"
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[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 = "regex"
+version = "1.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.5",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "renderdoc-sys"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"
+
+[[package]]
+name = "rodio"
+version = "0.17.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b1bb7b48ee48471f55da122c0044fcc7600cfcc85db88240b89cb832935e611"
+dependencies = [
+ "cpal",
+ "lewton",
+]
+
+[[package]]
+name = "ron"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
+dependencies = [
+ "base64",
+ "bitflags 2.4.2",
+ "serde",
+ "serde_derive",
+]
+
+[[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.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
+dependencies = [
+ "bitflags 2.4.2",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ruzstd"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d"
+dependencies = [
+ "byteorder",
+ "derive_more",
+ "twox-hash",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+
+[[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.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b2eaf3a5b264a521b988b2e73042e742df700c4f962cde845d1541adb46550"
+dependencies = [
+ "ab_glyph",
+ "log",
+ "memmap2",
+ "smithay-client-toolkit",
+ "tiny-skia",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
+
+[[package]]
+name = "serde"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[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.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a"
+dependencies = [
+ "bitflags 2.4.2",
+ "calloop",
+ "calloop-wayland-source",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix",
+ "thiserror",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
+[[package]]
+name = "smol_str"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49"
+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.4.2",
+]
+
+[[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 = "svg_fmt"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sysinfo"
+version = "0.30.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2"
+dependencies = [
+ "cfg-if",
+ "core-foundation-sys",
+ "libc",
+ "ntapi",
+ "once_cell",
+ "windows 0.52.0",
+]
+
+[[package]]
+name = "taffy"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c2287b6d7f721ada4cddf61ade5e760b2c6207df041cac9bfaa192897362fd3"
+dependencies = [
+ "arrayvec",
+ "grid",
+ "num-traits",
+ "slotmap",
+]
+
+[[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.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[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 = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log 0.2.0",
+]
+
+[[package]]
+name = "tracing-wasm"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07"
+dependencies = [
+ "tracing",
+ "tracing-subscriber",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "ttf-parser"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
+
+[[package]]
+name = "twox-hash"
+version = "1.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
+dependencies = [
+ "cfg-if",
+ "static_assertions",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
+[[package]]
+name = "uuid"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
+dependencies = [
+ "getrandom",
+ "serde",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f"
+dependencies = [
+ "bitflags 2.4.2",
+ "rustix",
+ "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.4.2",
+ "cursor-icon",
+ "wayland-backend",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba"
+dependencies = [
+ "rustix",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
+dependencies = [
+ "bitflags 2.4.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-plasma"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479"
+dependencies = [
+ "bitflags 2.4.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6"
+dependencies = [
+ "bitflags 2.4.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af"
+dependencies = [
+ "dlib",
+ "log",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wgpu"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfe9a310dcf2e6b85f00c46059aaeaf4184caa8e29a1ecd4b7a704c3482332d"
+dependencies = [
+ "arrayvec",
+ "cfg-if",
+ "cfg_aliases",
+ "js-sys",
+ "log",
+ "naga",
+ "parking_lot",
+ "profiling",
+ "raw-window-handle 0.6.0",
+ "smallvec",
+ "static_assertions",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "wgpu-core",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-core"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b15e451d4060ada0d99a64df44e4d590213496da7c4f245572d51071e8e30ed"
+dependencies = [
+ "arrayvec",
+ "bit-vec",
+ "bitflags 2.4.2",
+ "cfg_aliases",
+ "codespan-reporting",
+ "indexmap",
+ "log",
+ "naga",
+ "once_cell",
+ "parking_lot",
+ "profiling",
+ "raw-window-handle 0.6.0",
+ "rustc-hash",
+ "smallvec",
+ "thiserror",
+ "web-sys",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-hal"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bb47856236bfafc0bc591a925eb036ac19cd987624a447ff353e7a7e7e6f72"
+dependencies = [
+ "android_system_properties",
+ "arrayvec",
+ "ash",
+ "bit-set",
+ "bitflags 2.4.2",
+ "block",
+ "cfg_aliases",
+ "core-graphics-types",
+ "d3d12",
+ "glow",
+ "glutin_wgl_sys",
+ "gpu-alloc",
+ "gpu-allocator",
+ "gpu-descriptor",
+ "hassle-rs",
+ "js-sys",
+ "khronos-egl",
+ "libc",
+ "libloading 0.8.1",
+ "log",
+ "metal",
+ "naga",
+ "objc",
+ "once_cell",
+ "parking_lot",
+ "profiling",
+ "range-alloc",
+ "raw-window-handle 0.6.0",
+ "renderdoc-sys",
+ "rustc-hash",
+ "smallvec",
+ "thiserror",
+ "wasm-bindgen",
+ "web-sys",
+ "wgpu-types",
+ "winapi",
+]
+
+[[package]]
+name = "wgpu-types"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "895fcbeb772bfb049eb80b2d6e47f6c9af235284e9703c96fc0218a42ffd5af2"
+dependencies = [
+ "bitflags 2.4.2",
+ "js-sys",
+ "web-sys",
+]
+
+[[package]]
+name = "widestring"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.52.3",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.3",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.3",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.3",
+ "windows_aarch64_msvc 0.52.3",
+ "windows_i686_gnu 0.52.3",
+ "windows_i686_msvc 0.52.3",
+ "windows_x86_64_gnu 0.52.3",
+ "windows_x86_64_gnullvm 0.52.3",
+ "windows_x86_64_msvc 0.52.3",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
+
+[[package]]
+name = "winit"
+version = "0.29.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c824f11941eeae66ec71111cc2674373c772f482b58939bb4066b642aa2ffcf"
+dependencies = [
+ "ahash",
+ "android-activity",
+ "atomic-waker",
+ "bitflags 2.4.2",
+ "bytemuck",
+ "calloop",
+ "cfg_aliases",
+ "core-foundation",
+ "core-graphics",
+ "cursor-icon",
+ "icrate",
+ "js-sys",
+ "libc",
+ "log",
+ "memmap2",
+ "ndk 0.8.0",
+ "ndk-sys 0.5.0+25.2.9519653",
+ "objc2 0.4.1",
+ "once_cell",
+ "orbclient",
+ "percent-encoding",
+ "raw-window-handle 0.6.0",
+ "redox_syscall 0.3.5",
+ "rustix",
+ "sctk-adwaita",
+ "smithay-client-toolkit",
+ "smol_str",
+ "unicode-segmentation",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-plasma",
+ "web-sys",
+ "web-time",
+ "windows-sys 0.48.0",
+ "x11-dl",
+ "x11rb",
+ "xkbcommon-dl",
+]
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[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.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a"
+dependencies = [
+ "as-raw-xcb-connection",
+ "gethostname",
+ "libc",
+ "libloading 0.8.1",
+ "once_cell",
+ "rustix",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34"
+
+[[package]]
+name = "xcursor"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
+
+[[package]]
+name = "xi-unicode"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"
+
+[[package]]
+name = "xkbcommon-dl"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
+dependencies = [
+ "bitflags 2.4.2",
+ "dlib",
+ "log",
+ "once_cell",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
new file mode 100644
index 0000000..cc5a0ff
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "otterhide"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bevy = { version = "0.13", features = [ "wayland" ] }
+derive_more = "0.99.17"
+itertools = "0.12.1"
+js-sys = "0.3.68"
+rand = "0.8.5"
+wasm-bindgen = "0.2.91"
+
+[profile.dev.package."*"]
+opt-level = "z"
new file mode 100644
index 0000000..14ad37a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,147 @@
+include Makefile.d/defaults.mk
+
+all: ## Build the program (DEFAULT)
+all: test build
+.PHONY: all
+
+build: $(shell find src/)
+build: Cargo.lock
+build:
+	cargo build --release
+.PHONY: build
+
+test:
+	cargo test
+.PHONY: test
+
+install: ## Install the program in the ${prefix} directory
+install: prefix ?= $(out)
+install: build
+	test $(prefix)
+	mkdir --parents $(prefix)
+	cp --recursive $</* $(prefix)/
+.PHONY: install
+
+
+### DEVELOPMENT
+
+develop: ## Rebuild and run a development version of the program
+develop:
+	cargo run --features bevy/dynamic_linking
+.PHONY: develop
+
+clean: ## Remove all build artifacts
+clean:
+	git clean -dfX \
+		--exclude='!.envrc.private'
+.PHONY: clean
+
+serve: ## Serve the web version of the program
+serve: web
+	miniserve --interfaces=127.0.0.1 --index=index.html web
+.PHONY: serve
+
+help: ## Print this help message
+help:
+	@
+	echo "Useage: make [ goals ]"
+	echo
+	echo "Available goals:"
+	echo
+	cat $(MAKEFILE_LIST) | awk -f Makefile.d/make-goals.awk
+.PHONY: help
+
+### WEB TARGET
+
+web: ## Build the program for the web
+web: $(shell find src/)
+web: $(shell find web-assets/)
+web: Cargo.lock
+web: index.html
+web: base_url ?= "/"
+web:
+	trunk build \
+		--release \
+		--public-url $(base_url) \
+		--dist $@
+	touch $@
+
+web/develop: ## Rebuild and serve a development version of the in a local server
+web/develop:
+	trunk serve \
+		--address=0.0.0.0 \
+		index.html
+.PHONY: web/develop
+
+web/serve: ## Serve a release version of the program
+web/serve: web
+web/serve:
+	miniserve --index=index.html $<
+.PHONY: web/serve
+
+
+### CONTINUOUS INTEGRATION
+
+public: ## Prepare the web assets (used in CI)
+public: web
+public:
+	rm --recursive --force $@
+	cp --recursive --force $^ $@
+	unset GZIP
+	find $@ -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\|wasm\)$$' -exec gzip -9 -f -k {} \;
+	find $@ -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\|wasm\)$$' -exec brotli --force --keep {} \;
+	touch $@
+.PHONY: public
+
+
+### NIX SPECIFIC
+
+export nix := nix --experimental-features "nix-command flakes"
+export nix-cache-name := software-garden
+
+result: ## Build the program using Nix
+result:
+	cachix use $(nix-cache-name)
+	$(nix) build --print-build-logs
+
+nix-cache: ## Push Nix binary cache to Cachix
+nix-cache: result
+	$(nix) flake archive --json \
+	| jq --raw-output '.path, (.inputs | to_entries [] .value.path)' \
+	| cachix push $(nix-cache-name)
+
+	$(nix) path-info --recursive \
+	| cachix push $(nix-cache-name)
+
+
+### OCI BUILD IMAGE RELATED
+
+build-image: ## Create an OCI image with build environment (to be used in CI)
+build-image: flake.nix
+build-image: flake.lock
+build-image: rust-toolchain.toml
+build-image:
+	nix build \
+		--print-build-logs \
+		.#docker-image
+	cp --dereference result $@
+
+load-build-image: ## Load the OCI image using Podman
+load-build-image: build-image
+	gzip --decompress $^ --to-stdout | podman image load
+.PHONY: load-build-image
+
+build-inside-container: ## Build the program in an OCI container (e.g. to test CI)
+build-inside-container: load-build-image
+	# We need to know the project name to know which image to load. Can we do better?
+	test $${project_name}
+	podman run \
+		--rm \
+		--interactive \
+		--tty \
+		--volume ${PWD}:/src \
+		--workdir /src \
+		--entrypoint make \
+		localhost/$(project_name)-build-image \
+		public
+.PHONY: build-inside-container
new file mode 100644
index 0000000..1a788da
--- /dev/null
+++ b/Makefile.d/defaults.mk
@@ -0,0 +1,39 @@
+# Default settings for Make
+
+# Always use strict bash
+SHELL := bash
+.SHELLFLAGS := -eu -o pipefail -c
+.ONESHELL:
+
+# If a recipe fails, delete it's targets to avoid broken state
+.DELETE_ON_ERROR:
+
+# Allow $$(variables) to be expanded in a second pass
+.SECONDEXPANSION:
+
+# Warn about undefined variables and
+# TODO: How to make it an error, like in bash strict mode?
+# TODO: Consider https://www.artificialworlds.net/blog/2015/04/22/treat-warnings-as-errors-in-a-gnu-makefile/
+MAKEFLAGS += --warn-undefined-variables
+MAKEFLAGS += --warn-undefined-functions
+
+# Remove implicit rules - no magic please
+# See https://www.gnu.org/software/make/manual/html_node/Catalogue-of-Rules.html
+MAKEFLAGS += --no-builtin-rules
+
+# Helper functions
+
+define slugify
+$(shell \
+	echo $(1) \
+		| tr '[:upper:]' '[:lower:]' \
+		| tr --squeeze-repeats --complement '[:alnum:]\n' '-' \
+)
+endef
+
+# General purpose variables
+
+author = $(shell git config user.name)
+
+timestamp = $(shell date '+%s')
+
new file mode 100644
index 0000000..ca79030
--- /dev/null
+++ b/Makefile.d/make-goals.awk
@@ -0,0 +1,4 @@
+# Section header
+match($0, /^### (.+)/, matches)        { printf "\n\n%s:\n\n", matches[1] }
+# Goal with description
+match($0, /^(.+):.* ## (.+)/, matches) { printf "  %-30s %s\n", matches[1], matches[2]}
new file mode 100644
index 0000000..e352084
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,130 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1705309234,
+        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_2": {
+      "inputs": {
+        "systems": "systems_2"
+      },
+      "locked": {
+        "lastModified": 1705309234,
+        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1708807242,
+        "narHash": "sha256-sRTRkhMD4delO/hPxxi+XwLqPn8BuUq6nnj4JqLwOu0=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "73de017ef2d18a04ac4bfd0c02650007ccb31c2a",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs_2": {
+      "locked": {
+        "lastModified": 1706487304,
+        "narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "90f456026d284c22b3e3497be980b2e47d0b28ac",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs",
+        "rust-overlay": "rust-overlay"
+      }
+    },
+    "rust-overlay": {
+      "inputs": {
+        "flake-utils": "flake-utils_2",
+        "nixpkgs": "nixpkgs_2"
+      },
+      "locked": {
+        "lastModified": 1708913568,
+        "narHash": "sha256-76PGANC2ADf0h7fe0w2nWpfdGN+bemFs2rvW2EdU/ZY=",
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "rev": "cbdf3e5bb205ff2ca165fe661fbd6d885cbd0106",
+        "type": "github"
+      },
+      "original": {
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "type": "github"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
+    "systems_2": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
new file mode 100644
index 0000000..5795f21
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,89 @@
+{
+  description = "Simulate and explore a history of a town";
+
+  inputs = {
+    nixpkgs.url      = "github:NixOS/nixpkgs/nixos-unstable";
+    rust-overlay.url = "github:oxalica/rust-overlay";
+    flake-utils.url  = "github:numtide/flake-utils";
+  };
+
+  outputs = { self, nixpkgs, rust-overlay, flake-utils }:
+    flake-utils.lib.eachDefaultSystem (system:
+      let
+        project-name = "otterhide";
+        overlays = [ (import rust-overlay) ];
+        pkgs = import nixpkgs {
+          inherit system overlays;
+        };
+        buildInputs = with pkgs; [
+          openssl
+          alsa-lib
+          udev
+          libxkbcommon
+          vulkan-loader
+          wayland
+          libffi
+        ];
+        nativeBuildInputs = with pkgs; [
+          (rust-bin.fromRustupToolchainFile ./rust-toolchain.toml)
+          gcc
+          pkg-config
+          gnumake
+          findutils
+          gzip
+          lld
+          trunk
+          wasm-bindgen-cli
+          brotli
+          rustPlatform.bindgenHook
+        ];
+      in
+      {
+        devShell = pkgs.mkShell {
+          inherit buildInputs nativeBuildInputs;
+          name = "${project-name}-develpoment-shell";
+          packages = with pkgs; [
+            pkgs.rust-analyzer
+            pkgs.miniserve
+            pkgs.jq
+          ];
+          project_name = project-name; # Expose as an environment variable for make
+          LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${ with pkgs; lib.makeLibraryPath buildInputs }";
+          XCURSOR_THEME = "Adwaita"; # Workaround for https://github.com/bevyengine/bevy/issues/4768
+        };
+        defaultPackage = pkgs.rustPlatform.buildRustPackage {
+          # TODO: Consider using naersk here
+          name = project-name;
+          version = "1.0.0";
+          src = ./.;
+          cargoLock = {
+            lockFile = ./Cargo.lock;
+          };
+          postFixup = ''
+            # See https://discourse.nixos.org/t/rust-bevy-vulkan-loader-and-ld-library-path-variable/25282/2
+            patchelf --add-rpath ${ pkgs.vulkan-loader }/lib $out/bin/*
+          '';
+
+          inherit buildInputs nativeBuildInputs;
+        };
+        packages.docker-image = pkgs.dockerTools.buildLayeredImage {
+          name = "${project-name}-build-image";
+          tag = "latest";
+          created = "now";
+          contents = buildInputs ++ nativeBuildInputs ++ [
+            pkgs.bash
+            pkgs.coreutils
+            pkgs.git
+            pkgs.httpie
+            pkgs.rustup
+            pkgs.cacert
+          ];
+          fakeRootCommands = ''
+            mkdir --parents /tmp
+          '';
+          enableFakechroot = true;
+          config.Cmd = [ "${ pkgs.bash }/bin/bash" ];
+        };
+      }
+    );
+}
new file mode 100644
index 0000000..d11a782
--- /dev/null
+++ b/index.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<html class="no-js" lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="x-ua-compatible" content="ie=edge">
+        <title>Otterhide</title>
+        <meta name="description" content="Simulate and explore a history of a town">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+
+        <base data-trunk-public-url />
+
+        <link data-trunk rel="copy-dir" href="./web-assets/" data-target-path="./" />
+        <link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon.png" />
+        <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png" />
+        <link rel="icon" type="image/png" sizes="16x16" href="./favicon-16x16.png" />
+        <link rel="manifest" href="./site.webmanifest" />
+
+        <link data-trunk rel="copy-dir" href="./assets/" />
+
+        <style type="text/css" media="screen">
+         html, body {
+             margin: 0;
+             height: 100%;
+             overflow: hidden;
+         }
+         #game-canvas {
+             width: 100%;
+             height: 100%;
+         }
+        </style>
+        <script type="text/javascript">
+         // Workaround for autofocus not working in Firefox.
+         // See https://bugzilla.mozilla.org/show_bug.cgi?id=662496
+         document.addEventListener ("DOMContentLoaded", () => {
+             const canvas = document.getElementById ("game-canvas")
+             canvas.focus ()
+         })
+        </script>
+
+        <link data-trunk rel="rust" data-opt="z" />
+
+    </head>
+    <body style="touch-action: none">
+        <canvas id="game-canvas" tabindex="0" autofocus></canvas>
+    </body>
+</html>
new file mode 100644
index 0000000..571e68f
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,5 @@
+[toolchain]
+channel = "stable"
+components = [ "rust-src" ]
+targets = [ "wasm32-unknown-unknown" ]
+profile = "default"
new file mode 100644
index 0000000..80a1832
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+  println!("Hello, world!");
+}
new file mode 100644
index 0000000..eb8c79a
--- /dev/null
+++ b/web-assets/.gitkeep
@@ -0,0 +1 @@
+Just make sure this directory is checked in, even if empty. Otherwise Trunk will fail to build.
new file mode 100644
index 0000000..e694967
--- /dev/null
+++ b/web-assets/about.txt
@@ -0,0 +1,6 @@
+This favicon was generated using the following font:
+
+- Font Title: Leckerli One
+- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
+- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
+- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
new file mode 100644
index 0000000..6de8fb6
Binary files /dev/null and b/web-assets/android-chrome-192x192.png differ
new file mode 100644
index 0000000..5a71519
Binary files /dev/null and b/web-assets/android-chrome-512x512.png differ
new file mode 100644
index 0000000..0ea5fb1
Binary files /dev/null and b/web-assets/apple-touch-icon.png differ
new file mode 100644
index 0000000..de26fe8
Binary files /dev/null and b/web-assets/favicon-16x16.png differ
new file mode 100644
index 0000000..e823849
Binary files /dev/null and b/web-assets/favicon-32x32.png differ
new file mode 100644
index 0000000..043fef2
Binary files /dev/null and b/web-assets/favicon.ico differ
new file mode 100644
index 0000000..45dc8a2
--- /dev/null
+++ b/web-assets/site.webmanifest
@@ -0,0 +1 @@
+{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
\ No newline at end of file

Ensure the assets/ directory exists

On by Tad Lispy

Otherwise Trunk fails to build.

new file mode 100644
index 0000000..eb8c79a
--- /dev/null
+++ b/assets/.gitkeep
@@ -0,0 +1 @@
+Just make sure this directory is checked in, even if empty. Otherwise Trunk will fail to build.

Setup a Bevy application

On by Tad Lispy

It will only print a log message once. But it's there!

index 14ad37a..477a482 100644
--- a/Makefile
+++ b/Makefile
@@ -30,6 +30,11 @@ develop:
=	cargo run --features bevy/dynamic_linking
=.PHONY: develop
=
+web/develop: ## Run a development web server
+web/develop:
+	trunk serve
+.PHONY: web/develop
+
=clean: ## Remove all build artifacts
=clean:
=	git clean -dfX \
index 80a1832..0ec9c21 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,12 @@
+use bevy::prelude::*;
+
=fn main() {
-  println!("Hello, world!");
+    App::new()
+        .add_plugins(DefaultPlugins)
+        .add_systems(Startup, greet)
+        .run()
+}
+
+fn greet() {
+    info!("Let's build the city on rock and roll!")
=}

Make the canvas fill the viewport

On by Tad Lispy

index 0ec9c21..5f5962a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,15 @@ use bevy::prelude::*;
=
=fn main() {
=    App::new()
-        .add_plugins(DefaultPlugins)
+        .add_plugins(DefaultPlugins.set(WindowPlugin {
+            primary_window: Some(Window {
+                title: "Otterhide".into(),
+                canvas: Some("#game-canvas".into()),
+                mode: bevy::window::WindowMode::Fullscreen,
+                ..default()
+            }),
+            ..default()
+        }))
=        .add_systems(Startup, greet)
=        .run()
=}

Setup a ground plane and a camera

On by Tad Lispy

index 5f5962a..3789a71 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,10 +11,29 @@ fn main() {
=            }),
=            ..default()
=        }))
-        .add_systems(Startup, greet)
+        .add_systems(Startup, (greet, setup_ground, setup_camera))
=        .run()
=}
=
=fn greet() {
=    info!("Let's build the city on rock and roll!")
=}
+
+fn setup_ground(
+    mut commands: Commands,
+    mut meshes: ResMut<Assets<Mesh>>,
+    mut materials: ResMut<Assets<StandardMaterial>>,
+) {
+    commands.spawn(PbrBundle {
+        mesh: meshes.add(Plane3d::default().mesh().size(20., 20.)),
+        material: materials.add(Color::hsl(120.0, 0.6, 0.8)),
+        ..default()
+    });
+}
+
+fn setup_camera(mut commands: Commands) {
+    commands.spawn(Camera3dBundle {
+        transform: Transform::from_xyz(0., 10., 20.).looking_at(Vec3::ZERO, Vec3::Y),
+        ..default()
+    });
+}

Setup a house (cuboid) and sunlight

On by Tad Lispy

Tweak parameters to show the perspective and make the ground color darker and less saturated.

index 3789a71..00eefa4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,7 +11,16 @@ fn main() {
=            }),
=            ..default()
=        }))
-        .add_systems(Startup, (greet, setup_ground, setup_camera))
+        .add_systems(
+            Startup,
+            (
+                greet,
+                setup_ground,
+                setup_house,
+                setup_sunlight,
+                setup_camera,
+            ),
+        )
=        .run()
=}
=
@@ -26,14 +35,40 @@ fn setup_ground(
=) {
=    commands.spawn(PbrBundle {
=        mesh: meshes.add(Plane3d::default().mesh().size(20., 20.)),
-        material: materials.add(Color::hsl(120.0, 0.6, 0.8)),
+        material: materials.add(Color::hsl(150.0, 0.3, 0.3)),
+        ..default()
+    });
+}
+
+fn setup_house(
+    mut commands: Commands,
+    mut meshes: ResMut<Assets<Mesh>>,
+    mut materials: ResMut<Assets<StandardMaterial>>,
+) {
+    const SIZE: f32 = 1.;
+
+    commands.spawn(PbrBundle {
+        mesh: meshes.add(Cuboid::new(SIZE, SIZE * 2., SIZE)),
+        material: materials.add(Color::hsl(60.0, 0.8, 0.5)),
+        transform: Transform::from_xyz(0., SIZE, 0.),
=        ..default()
=    });
=}
=
=fn setup_camera(mut commands: Commands) {
=    commands.spawn(Camera3dBundle {
-        transform: Transform::from_xyz(0., 10., 20.).looking_at(Vec3::ZERO, Vec3::Y),
+        transform: Transform::from_xyz(5., 10., 20.).looking_at(Vec3::ZERO, Vec3::Y),
+        ..default()
+    });
+}
+
+fn setup_sunlight(mut commands: Commands) {
+    commands.spawn(DirectionalLightBundle {
+        transform: Transform::from_xyz(30., 40., -20.).looking_at(Vec3::ZERO, Vec3::Y),
+        directional_light: DirectionalLight {
+            shadows_enabled: true,
+            ..default()
+        },
=        ..default()
=    });
=}

Set the camera in motion

On by Tad Lispy

index 00eefa4..ebc5693 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,6 +21,7 @@ fn main() {
=                setup_camera,
=            ),
=        )
+        .add_systems(Update, orbit_camera)
=        .run()
=}
=
@@ -72,3 +73,9 @@ fn setup_sunlight(mut commands: Commands) {
=        ..default()
=    });
=}
+
+fn orbit_camera(mut camera: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
+    let mut transform = camera.single_mut();
+
+    transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 5.))
+}

Remove duplicated web/develop goal from the Makefile

On by Tad Lispy

index 477a482..14ad37a 100644
--- a/Makefile
+++ b/Makefile
@@ -30,11 +30,6 @@ develop:
=	cargo run --features bevy/dynamic_linking
=.PHONY: develop
=
-web/develop: ## Run a development web server
-web/develop:
-	trunk serve
-.PHONY: web/develop
-
=clean: ## Remove all build artifacts
=clean:
=	git clean -dfX \

Use a gltf model for a house and a sculpture

On by Tad Lispy

The source .blend files sit in the art/ directory. The exported .gltf files go to the assets/ directory, and are loaded from there.

To make loader work it was necessary to disable Trunk's SPA feature. That's because Bevy application will try to fetch .meta file for each resource. If there is no such file, the server should respond with 404 status and the loader will just use default parameters. But with the SPA feature Trunk always responds with status 200 and contents of index.html file. Then Bevy tries to parse this HTML and fails.

index 14ad37a..d2398f5 100644
--- a/Makefile
+++ b/Makefile
@@ -70,6 +70,7 @@ web/develop: ## Rebuild and serve a development version of the in a local server
=web/develop:
=	trunk serve \
=		--address=0.0.0.0 \
+		--no-spa \
=		index.html
=.PHONY: web/develop
=
new file mode 100644
index 0000000..7284b65
Binary files /dev/null and b/art/large_buildingB.blend differ
new file mode 100644
index 0000000..16a64d8
Binary files /dev/null and b/art/suzanne.blend differ
new file mode 100644
index 0000000..0e55573
Binary files /dev/null and b/assets/large_buildingB.glb differ
new file mode 100644
index 0000000..8b86589
Binary files /dev/null and b/assets/suzanne.glb differ
index ebc5693..b360944 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,6 +17,7 @@ fn main() {
=                greet,
=                setup_ground,
=                setup_house,
+                setup_sculpture,
=                setup_sunlight,
=                setup_camera,
=            ),
@@ -41,17 +42,22 @@ fn setup_ground(
=    });
=}
=
-fn setup_house(
-    mut commands: Commands,
-    mut meshes: ResMut<Assets<Mesh>>,
-    mut materials: ResMut<Assets<StandardMaterial>>,
-) {
-    const SIZE: f32 = 1.;
+fn setup_house(mut commands: Commands, assets: ResMut<AssetServer>) {
+    let model = assets.load("large_buildingB.glb#Scene0");
=
-    commands.spawn(PbrBundle {
-        mesh: meshes.add(Cuboid::new(SIZE, SIZE * 2., SIZE)),
-        material: materials.add(Color::hsl(60.0, 0.8, 0.5)),
-        transform: Transform::from_xyz(0., SIZE, 0.),
+    commands.spawn(SceneBundle {
+        scene: model,
+        transform: Transform::from_xyz(0., 0., 0.),
+        ..default()
+    });
+}
+
+fn setup_sculpture(mut commands: Commands, assets: ResMut<AssetServer>) {
+    let model = assets.load("suzanne.glb#Scene0");
+
+    commands.spawn(SceneBundle {
+        scene: model,
+        transform: Transform::from_xyz(3., 0., 3.),
=        ..default()
=    });
=}

Update some Cargo dependencies

On by Tad Lispy

index 8bb2117..be9d0ed 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -386,7 +386,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -452,7 +452,7 @@ checksum = "028ae2a34678055185d7f1beebb1ebe6a8dcf3733e139e4ee1383a7f29ae8ba6"
=dependencies = [
= "bevy_macro_utils",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -500,7 +500,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -560,7 +560,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -686,7 +686,7 @@ dependencies = [
= "proc-macro2",
= "quote",
= "rustc-hash",
- "syn 2.0.50",
+ "syn 2.0.51",
= "toml_edit 0.21.1",
=]
=
@@ -767,7 +767,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
= "uuid",
=]
=
@@ -825,7 +825,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -993,7 +993,7 @@ checksum = "31ae98e9c0c08b0f5c90e22cd713201f759b98d4fd570b99867a695f8641859a"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -1055,7 +1055,7 @@ dependencies = [
= "regex",
= "rustc-hash",
= "shlex",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -1184,7 +1184,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -1594,7 +1594,7 @@ checksum = "92959a9e8d13eaa13b8ae8c7b583c3bf1669ca7a8e7708a088d12587ba86effc"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -1734,7 +1734,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -1885,7 +1885,7 @@ dependencies = [
= "inflections",
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -2575,7 +2575,7 @@ dependencies = [
= "proc-macro-crate 3.1.0",
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -3145,7 +3145,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -3281,9 +3281,9 @@ dependencies = [
=
=[[package]]
=name = "syn"
-version = "2.0.50"
+version = "2.0.51"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
+checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
=dependencies = [
= "proc-macro2",
= "quote",
@@ -3342,7 +3342,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -3442,7 +3442,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
=
=[[package]]
@@ -3611,7 +3611,7 @@ dependencies = [
= "once_cell",
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
= "wasm-bindgen-shared",
=]
=
@@ -3645,7 +3645,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
= "wasm-bindgen-backend",
= "wasm-bindgen-shared",
=]
@@ -4188,9 +4188,9 @@ checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
=
=[[package]]
=name = "winit"
-version = "0.29.10"
+version = "0.29.11"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c824f11941eeae66ec71111cc2674373c772f482b58939bb4066b642aa2ffcf"
+checksum = "272be407f804517512fdf408f0fe6c067bf24659a913c61af97af176bfd5aa92"
=dependencies = [
= "ahash",
= "android-activity",
@@ -4329,5 +4329,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.50",
+ "syn 2.0.51",
=]
index cc5a0ff..82a0963 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
=# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
=
=[dependencies]
-bevy = { version = "0.13", features = [ "wayland" ] }
+bevy = { version = "0.13", features = ["wayland"] }
=derive_more = "0.99.17"
=itertools = "0.12.1"
=js-sys = "0.3.68"

Implement basic logic concerning day-month

On by Tad Lispy

A day-month is an abstraction of a month, where every day is the same, so it collapses into one. There is no day-of-month component. only year, month and time of day. It can be advanced (mutation in place) and displayed. The components can be extracted. A Month value can be converted to and from an i32 with overflowing (1 = January, 0 = 12 = 24 = -12 = December).

index d2398f5..493e07b 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ build:
=.PHONY: build
=
=test:
-	cargo test
+	cargo test --features bevy/dynamic_linking
=.PHONY: test
=
=install: ## Install the program in the ${prefix} directory
new file mode 100644
index 0000000..8a58156
--- /dev/null
+++ b/src/day_month.rs
@@ -0,0 +1,167 @@
+use std::fmt::Display;
+
+pub const HOUR: f32 = 1.0 / 24.0;
+pub const MINUTE: f32 = HOUR / 60.0;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum Month {
+    January = 1,
+    February,
+    March,
+    April,
+    May,
+    June,
+    July,
+    August,
+    September,
+    October,
+    November,
+    December,
+}
+
+impl Display for Month {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{self:?}")
+    }
+}
+
+impl Month {
+    fn new(number: i32) -> Self {
+        match number.rem_euclid(12) {
+            0 => Self::December,
+            1 => Self::January,
+            2 => Self::February,
+            3 => Self::March,
+            4 => Self::April,
+            5 => Self::May,
+            6 => Self::June,
+            7 => Self::July,
+            8 => Self::August,
+            9 => Self::September,
+            10 => Self::October,
+            11 => Self::November,
+            _ => panic!("Euclidian remainder of 12 outside of 0..11 range"),
+        }
+    }
+}
+
+#[cfg(test)]
+mod month_tests {
+    use super::*;
+
+    #[test]
+    fn month_to_number() {
+        assert_eq!(Month::January as i32, 1);
+        assert_eq!(Month::February as i32, 2);
+        assert_eq!(Month::December as i32, 12);
+    }
+
+    #[test]
+    fn number_to_month() {
+        assert_eq!(Month::new(1), Month::January);
+        assert_eq!(Month::new(2), Month::February);
+        assert_eq!(Month::new(12), Month::December);
+
+        // Overflow
+        assert_eq!(Month::new(13), Month::January);
+        assert_eq!(Month::new(14), Month::February);
+
+        // Negative
+        assert_eq!(Month::new(0), Month::December);
+        assert_eq!(Month::new(-1), Month::November);
+        assert_eq!(Month::new(-13), Month::November);
+    }
+}
+
+/// Represents a month in a day
+pub struct DayMonth {
+    /// How many daymonths have passed since the start
+    value: f32,
+    /// The first year when the city was founded
+    start: i32,
+}
+
+impl DayMonth {
+    /// Initialize the day-month. Returns beginning (midnight) of the January of a given start year.
+    pub fn new(year: i32) -> Self {
+        Self {
+            start: year,
+            value: 0.0,
+        }
+    }
+
+    pub fn advance(&mut self, duration: f32) -> &Self {
+        self.value += duration;
+        self
+    }
+
+    pub fn year(&self) -> i32 {
+        self.value.div_euclid(12.0) as i32 + self.start
+    }
+
+    pub fn month(&self) -> Month {
+        Month::new(self.value as i32 + 1)
+    }
+
+    pub fn hour(&self) -> i32 {
+        (self.value.rem_euclid(1.0) * 24.0) as i32
+    }
+
+    pub fn minute(&self) -> f32 {
+        // TODO: Can minutes calculation be simplified for efficiency?
+        (self.value.rem_euclid(1.0) * 24.0).rem_euclid(1.0) * 60.0
+    }
+}
+
+impl Display for DayMonth {
+    /// Should give something like March 2024 13:32
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{} {} {:02}:{:02.0}",
+            self.month(),
+            self.year(),
+            self.hour(),
+            self.minute()
+        )
+    }
+}
+
+#[cfg(test)]
+mod daymonth_tests {
+    use super::*;
+
+    #[test]
+    fn new() {
+        let daymonth = DayMonth::new(1972);
+
+        assert_eq!(daymonth.year(), 1972);
+        assert_eq!(daymonth.month(), Month::January);
+        assert_eq!(daymonth.hour(), 0);
+        assert_eq!(daymonth.minute(), 0.0);
+    }
+
+    #[test]
+    fn advance_and_format() {
+        let mut daymonth = DayMonth::new(1984);
+        assert_eq!(format!("{daymonth}"), "January 1984 00:00");
+
+        daymonth.advance(1.0);
+        assert_eq!(format!("{daymonth}"), "February 1984 00:00");
+
+        daymonth.advance(45. * MINUTE);
+        assert_eq!(format!("{daymonth}"), "February 1984 00:45");
+
+        daymonth.advance(23. * HOUR);
+        assert_eq!(format!("{daymonth}"), "February 1984 23:45");
+
+        daymonth.advance(30. * MINUTE);
+        assert_eq!(format!("{daymonth}"), "March 1984 00:15");
+
+        daymonth.advance(13.5 * HOUR);
+        assert_eq!(format!("{daymonth}"), "March 1984 13:45");
+
+        daymonth.advance(12. * HOUR);
+        assert_eq!(format!("{daymonth}"), "April 1984 01:45");
+    }
+}
index b360944..cd3d051 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,5 @@
+mod day_month;
+
=use bevy::prelude::*;
=
=fn main() {

Let the time pass but keep an eye on it

On by Tad Lispy

index cd3d051..57b74df 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
=mod day_month;
=
=use bevy::prelude::*;
+use day_month::DayMonth;
=
=fn main() {
=    App::new()
@@ -13,6 +14,7 @@ fn main() {
=            }),
=            ..default()
=        }))
+        .insert_resource(Date(DayMonth::new(1875)))
=        .add_systems(
=            Startup,
=            (
@@ -21,13 +23,17 @@ fn main() {
=                setup_house,
=                setup_sculpture,
=                setup_sunlight,
+                setup_date_display,
=                setup_camera,
=            ),
=        )
-        .add_systems(Update, orbit_camera)
+        .add_systems(Update, (orbit_camera, advance_date, update_date_display))
=        .run()
=}
=
+#[derive(Resource)]
+struct Date(DayMonth);
+
=fn greet() {
=    info!("Let's build the city on rock and roll!")
=}
@@ -87,3 +93,40 @@ fn orbit_camera(mut camera: Query<&mut Transform, With<Camera>>, time: Res<Time>
=
=    transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 5.))
=}
+
+fn setup_date_display(mut commands: Commands, date: Res<Date>) {
+    commands
+        .spawn(
+            TextBundle::from_section(
+                date.0.to_string(),
+                TextStyle {
+                    font_size: 32.0,
+                    color: Color::WHITE,
+                    ..default()
+                },
+            )
+            .with_style(Style {
+                justify_self: JustifySelf::Center,
+                margin: UiRect::top(Val::Px(20.0)),
+                ..default()
+            }),
+        )
+        .insert(DateDisplay);
+}
+
+fn advance_date(time: Res<Time>, mut date: ResMut<Date>) {
+    // Simulation day-months per one second of real time
+    const SCALE: f32 = 1.0 / 60.0;
+
+    let delta = time.delta_seconds();
+    let duration = delta * SCALE;
+    date.0.advance(duration);
+}
+
+fn update_date_display(mut display: Query<&mut Text, With<DateDisplay>>, date: Res<Date>) {
+    let mut display = display.single_mut();
+    display.sections[0].value = date.0.to_string();
+}
+
+#[derive(Component)]
+struct DateDisplay;

Slow down the camera

On by Tad Lispy

index 57b74df..b20ee92 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,7 @@ mod day_month;
=
=use bevy::prelude::*;
=use day_month::DayMonth;
+use std::f32::consts::PI;
=
=fn main() {
=    App::new()
@@ -89,9 +90,14 @@ fn setup_sunlight(mut commands: Commands) {
=}
=
=fn orbit_camera(mut camera: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
-    let mut transform = camera.single_mut();
+    const RPS: f32 = 0.005;
+    const VELOCITY: f32 = PI * 2.0 * RPS;
=
-    transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 5.))
+    let mut transform = camera.single_mut();
+    transform.rotate_around(
+        Vec3::ZERO,
+        Quat::from_rotation_y(time.delta_seconds() * VELOCITY),
+    )
=}
=
=fn setup_date_display(mut commands: Commands, date: Res<Date>) {

Simulate a horizon

On by Tad Lispy

With a big circle instead of a small plane for ground and lower camera angle.

index b20ee92..94e13d1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,7 @@ mod day_month;
=
=use bevy::prelude::*;
=use day_month::DayMonth;
-use std::f32::consts::PI;
+use std::f32::consts::{FRAC_PI_2, PI};
=
=fn main() {
=    App::new()
@@ -44,9 +44,11 @@ fn setup_ground(
=    mut meshes: ResMut<Assets<Mesh>>,
=    mut materials: ResMut<Assets<StandardMaterial>>,
=) {
+    const SIZE: f32 = 100.;
=    commands.spawn(PbrBundle {
-        mesh: meshes.add(Plane3d::default().mesh().size(20., 20.)),
+        mesh: meshes.add(Circle::new(SIZE)),
=        material: materials.add(Color::hsl(150.0, 0.3, 0.3)),
+        transform: Transform::from_rotation(Quat::from_rotation_x(-FRAC_PI_2)),
=        ..default()
=    });
=}
@@ -80,7 +82,7 @@ fn setup_camera(mut commands: Commands) {
=
=fn setup_sunlight(mut commands: Commands) {
=    commands.spawn(DirectionalLightBundle {
-        transform: Transform::from_xyz(30., 40., -20.).looking_at(Vec3::ZERO, Vec3::Y),
+        transform: Transform::from_xyz(20., 25., -20.).looking_at(Vec3::ZERO, Vec3::Y),
=        directional_light: DirectionalLight {
=            shadows_enabled: true,
=            ..default()

On by Tad Lispy

The new module exposes a plugin.

new file mode 100644
index 0000000..393d63c
--- /dev/null
+++ b/src/camera.rs
@@ -0,0 +1,30 @@
+use std::f32::consts::FRAC_2_PI;
+
+use bevy::prelude::*;
+
+pub struct CameraPlugin {}
+
+impl Plugin for CameraPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(Startup, setup_camera)
+            .add_systems(Update, orbit_camera);
+    }
+}
+
+fn setup_camera(mut commands: Commands) {
+    commands.spawn(Camera3dBundle {
+        transform: Transform::from_xyz(5., 10., 20.).looking_at(Vec3::ZERO, Vec3::Y),
+        ..default()
+    });
+}
+
+fn orbit_camera(mut camera: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
+    const RPS: f32 = 0.005;
+    const VELOCITY: f32 = FRAC_2_PI * RPS;
+
+    let mut transform = camera.single_mut();
+    transform.rotate_around(
+        Vec3::ZERO,
+        Quat::from_rotation_y(time.delta_seconds() * VELOCITY),
+    )
+}
index 94e13d1..a12055a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,9 @@
+mod camera;
=mod day_month;
=
=use bevy::prelude::*;
=use day_month::DayMonth;
-use std::f32::consts::{FRAC_PI_2, PI};
+use std::f32::consts::FRAC_PI_2;
=
=fn main() {
=    App::new()
@@ -16,6 +17,7 @@ fn main() {
=            ..default()
=        }))
=        .insert_resource(Date(DayMonth::new(1875)))
+        .add_plugins(camera::CameraPlugin {})
=        .add_systems(
=            Startup,
=            (
@@ -25,10 +27,9 @@ fn main() {
=                setup_sculpture,
=                setup_sunlight,
=                setup_date_display,
-                setup_camera,
=            ),
=        )
-        .add_systems(Update, (orbit_camera, advance_date, update_date_display))
+        .add_systems(Update, (advance_date, update_date_display))
=        .run()
=}
=
@@ -73,13 +74,6 @@ fn setup_sculpture(mut commands: Commands, assets: ResMut<AssetServer>) {
=    });
=}
=
-fn setup_camera(mut commands: Commands) {
-    commands.spawn(Camera3dBundle {
-        transform: Transform::from_xyz(5., 10., 20.).looking_at(Vec3::ZERO, Vec3::Y),
-        ..default()
-    });
-}
-
=fn setup_sunlight(mut commands: Commands) {
=    commands.spawn(DirectionalLightBundle {
=        transform: Transform::from_xyz(20., 25., -20.).looking_at(Vec3::ZERO, Vec3::Y),
@@ -91,17 +85,6 @@ fn setup_sunlight(mut commands: Commands) {
=    });
=}
=
-fn orbit_camera(mut camera: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
-    const RPS: f32 = 0.005;
-    const VELOCITY: f32 = PI * 2.0 * RPS;
-
-    let mut transform = camera.single_mut();
-    transform.rotate_around(
-        Vec3::ZERO,
-        Quat::from_rotation_y(time.delta_seconds() * VELOCITY),
-    )
-}
-
=fn setup_date_display(mut commands: Commands, date: Res<Date>) {
=    commands
=        .spawn(

Parameterize the camera plugin

On by Tad Lispy

Also fix a bug in angular velocity calculation. It should be 2π * rps, not 2/π * rps.

index 393d63c..f466363 100644
--- a/src/camera.rs
+++ b/src/camera.rs
@@ -1,30 +1,43 @@
-use std::f32::consts::FRAC_2_PI;
-
=use bevy::prelude::*;
+use std::f32::consts::PI;
+
+#[derive(Resource, Clone, Copy)]
+pub struct CameraSettings {
+    pub elevation: f32,
+    pub distance: f32,
+    pub rps: f32,
+}
=
-pub struct CameraPlugin {}
+pub struct CameraPlugin(pub CameraSettings);
=
=impl Plugin for CameraPlugin {
=    fn build(&self, app: &mut App) {
-        app.add_systems(Startup, setup_camera)
+        let settings = self.0.clone();
+
+        app.insert_resource(settings)
+            .add_systems(Startup, setup_camera)
=            .add_systems(Update, orbit_camera);
=    }
=}
=
-fn setup_camera(mut commands: Commands) {
+fn setup_camera(mut commands: Commands, settings: Res<CameraSettings>) {
=    commands.spawn(Camera3dBundle {
-        transform: Transform::from_xyz(5., 10., 20.).looking_at(Vec3::ZERO, Vec3::Y),
+        transform: Transform::from_xyz(settings.distance, settings.elevation, 0.)
+            .looking_at(Vec3::ZERO, Vec3::Y),
=        ..default()
=    });
=}
=
-fn orbit_camera(mut camera: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
-    const RPS: f32 = 0.005;
-    const VELOCITY: f32 = FRAC_2_PI * RPS;
+fn orbit_camera(
+    mut camera: Query<&mut Transform, With<Camera>>,
+    time: Res<Time>,
+    settings: Res<CameraSettings>,
+) {
+    let velocity = PI * 2.0 * settings.rps;
=
=    let mut transform = camera.single_mut();
=    transform.rotate_around(
=        Vec3::ZERO,
-        Quat::from_rotation_y(time.delta_seconds() * VELOCITY),
+        Quat::from_rotation_y(time.delta_seconds() * velocity),
=    )
=}
index a12055a..e850684 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,7 +17,11 @@ fn main() {
=            ..default()
=        }))
=        .insert_resource(Date(DayMonth::new(1875)))
-        .add_plugins(camera::CameraPlugin {})
+        .add_plugins(camera::CameraPlugin(camera::CameraSettings {
+            elevation: 5.,
+            distance: 12.,
+            rps: 0.01,
+        }))
=        .add_systems(
=            Startup,
=            (

Implement day-night cycle with sunlight

On by Tad Lispy

The sun movement and sunlight intensity are in the sun module now. The implementation is pretty basic, but maybe good enough for now. I left the sun gizmo visible for now.

index 8a58156..76c684e 100644
--- a/src/day_month.rs
+++ b/src/day_month.rs
@@ -111,6 +111,11 @@ impl DayMonth {
=        // TODO: Can minutes calculation be simplified for efficiency?
=        (self.value.rem_euclid(1.0) * 24.0).rem_euclid(1.0) * 60.0
=    }
+
+    /// Return a number between 0.0 and 1.0 representing the fraction of the day that passed
+    pub fn time_of_day(&self) -> f32 {
+        self.value.rem_euclid(1.0)
+    }
=}
=
=impl Display for DayMonth {
index e850684..ae7dbd3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
=mod camera;
=mod day_month;
+mod sun;
=
=use bevy::prelude::*;
=use day_month::DayMonth;
@@ -22,6 +23,7 @@ fn main() {
=            distance: 12.,
=            rps: 0.01,
=        }))
+        .add_plugins(sun::SunPlugin)
=        .add_systems(
=            Startup,
=            (
@@ -29,7 +31,6 @@ fn main() {
=                setup_ground,
=                setup_house,
=                setup_sculpture,
-                setup_sunlight,
=                setup_date_display,
=            ),
=        )
@@ -78,17 +79,6 @@ fn setup_sculpture(mut commands: Commands, assets: ResMut<AssetServer>) {
=    });
=}
=
-fn setup_sunlight(mut commands: Commands) {
-    commands.spawn(DirectionalLightBundle {
-        transform: Transform::from_xyz(20., 25., -20.).looking_at(Vec3::ZERO, Vec3::Y),
-        directional_light: DirectionalLight {
-            shadows_enabled: true,
-            ..default()
-        },
-        ..default()
-    });
-}
-
=fn setup_date_display(mut commands: Commands, date: Res<Date>) {
=    commands
=        .spawn(
new file mode 100644
index 0000000..8e0f612
--- /dev/null
+++ b/src/sun.rs
@@ -0,0 +1,68 @@
+use std::{
+    f32::consts::PI,
+    ops::{Mul, Sub},
+};
+
+use crate::Date;
+use bevy::prelude::*;
+
+const ORBIT: f32 = 100.;
+
+pub struct SunPlugin;
+
+impl Plugin for SunPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(Startup, setup_sunlight)
+            .add_systems(Update, (move_sun, update_sunlight_color, draw_gizmos));
+    }
+}
+
+#[derive(Component)]
+struct Sun;
+
+fn draw_gizmos(mut gizmos: Gizmos, sun: Query<(&Transform, &DirectionalLight), With<Sun>>) {
+    let (position, light) = sun.single();
+
+    gizmos.sphere(position.translation, Quat::default(), 5., light.color);
+}
+
+fn setup_sunlight(mut commands: Commands) {
+    commands
+        .spawn(DirectionalLightBundle {
+            transform: Transform::from_translation(Vec3::NEG_Y * ORBIT)
+                .looking_at(Vec3::ZERO, Vec3::Y),
+            directional_light: DirectionalLight {
+                shadows_enabled: true,
+                color: Color::hsl(0.0, 1.0, 0.9),
+                ..default()
+            },
+            ..default()
+        })
+        .insert(Sun);
+}
+
+fn move_sun(mut sun: Query<&mut Transform, With<Sun>>, date: Res<Date>) {
+    let daytime = date.0.time_of_day();
+    let angle = daytime * 2.0 * PI - PI;
+    let rotation = Quat::from_rotation_x(angle) * Quat::from_rotation_z(1.0);
+    let translation = rotation.mul_vec3(Vec3::Y) * ORBIT;
+
+    let mut transform = sun.single_mut();
+    transform.translation = translation;
+    transform.look_at(Vec3::ZERO, Vec3::Y);
+}
+
+fn update_sunlight_color(mut sun: Query<&mut DirectionalLight, With<Sun>>, date: Res<Date>) {
+    let daytime = date.0.time_of_day();
+    let sine = daytime.sub(0.25).mul(2.0 * PI).sin();
+    let hue = 10.0 + (sine + 0.5) * 30.0;
+    let lightness = (sine + 1.0) * 0.1 + 0.75; // between 0.75 and 0.95
+    let illuminance = (sine + 0.5) / 2.0 * 32_000.0;
+
+    let mut sun = sun.single_mut();
+    sun.color.set_h(hue);
+    sun.color.set_l(lightness);
+    sun.illuminance = illuminance;
+}
+
+// TODO: Add ambient light. As it is, at noon shadows are too dark.

Format some code

On by Tad Lispy

index 8e0f612..0556e54 100644
--- a/src/sun.rs
+++ b/src/sun.rs
@@ -1,10 +1,7 @@
-use std::{
-    f32::consts::PI,
-    ops::{Mul, Sub},
-};
-
=use crate::Date;
=use bevy::prelude::*;
+use std::f32::consts::PI;
+use std::ops::{Mul, Sub};
=
=const ORBIT: f32 = 100.;
=

Separate date logic to a new plugin

On by Tad Lispy

new file mode 100644
index 0000000..e465f96
--- /dev/null
+++ b/src/date.rs
@@ -0,0 +1,56 @@
+use crate::day_month::DayMonth;
+use bevy::prelude::*;
+
+const SCALE: f32 = 1.0 / 60.0;
+
+pub struct DatePlugin {
+    pub start: i32,
+}
+
+impl Plugin for DatePlugin {
+    fn build(&self, app: &mut App) {
+        app.insert_resource(Date(DayMonth::new(self.start)))
+            .add_systems(Startup, setup_date_display)
+            .add_systems(Update, (advance_date, update_date_display));
+    }
+}
+
+#[derive(Resource)]
+pub struct Date(pub DayMonth);
+
+fn advance_date(time: Res<Time>, mut date: ResMut<Date>) {
+    // Simulation day-months per one second of real time
+
+    let delta = time.delta_seconds();
+    let duration = delta * SCALE;
+    date.0.advance(duration);
+}
+
+// TODO: Consider if date display logic should go to a UI module?
+#[derive(Component)]
+struct DateDisplay;
+
+fn setup_date_display(mut commands: Commands, date: Res<Date>) {
+    commands
+        .spawn(
+            TextBundle::from_section(
+                date.0.to_string(),
+                TextStyle {
+                    font_size: 32.0,
+                    color: Color::WHITE,
+                    ..default()
+                },
+            )
+            .with_style(Style {
+                justify_self: JustifySelf::Center,
+                margin: UiRect::top(Val::Px(20.0)),
+                ..default()
+            }),
+        )
+        .insert(DateDisplay);
+}
+
+fn update_date_display(mut display: Query<&mut Text, With<DateDisplay>>, date: Res<Date>) {
+    let mut display = display.single_mut();
+    display.sections[0].value = date.0.to_string();
+}
index ae7dbd3..c600def 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,10 @@
=mod camera;
+mod date;
=mod day_month;
=mod sun;
=
=use bevy::prelude::*;
-use day_month::DayMonth;
+use date::DatePlugin;
=use std::f32::consts::FRAC_PI_2;
=
=fn main() {
@@ -17,30 +18,17 @@ fn main() {
=            }),
=            ..default()
=        }))
-        .insert_resource(Date(DayMonth::new(1875)))
=        .add_plugins(camera::CameraPlugin(camera::CameraSettings {
=            elevation: 5.,
=            distance: 12.,
=            rps: 0.01,
=        }))
+        .add_plugins(DatePlugin { start: 1850 })
=        .add_plugins(sun::SunPlugin)
-        .add_systems(
-            Startup,
-            (
-                greet,
-                setup_ground,
-                setup_house,
-                setup_sculpture,
-                setup_date_display,
-            ),
-        )
-        .add_systems(Update, (advance_date, update_date_display))
+        .add_systems(Startup, (greet, setup_ground, setup_house, setup_sculpture))
=        .run()
=}
=
-#[derive(Resource)]
-struct Date(DayMonth);
-
=fn greet() {
=    info!("Let's build the city on rock and roll!")
=}
@@ -78,40 +66,3 @@ fn setup_sculpture(mut commands: Commands, assets: ResMut<AssetServer>) {
=        ..default()
=    });
=}
-
-fn setup_date_display(mut commands: Commands, date: Res<Date>) {
-    commands
-        .spawn(
-            TextBundle::from_section(
-                date.0.to_string(),
-                TextStyle {
-                    font_size: 32.0,
-                    color: Color::WHITE,
-                    ..default()
-                },
-            )
-            .with_style(Style {
-                justify_self: JustifySelf::Center,
-                margin: UiRect::top(Val::Px(20.0)),
-                ..default()
-            }),
-        )
-        .insert(DateDisplay);
-}
-
-fn advance_date(time: Res<Time>, mut date: ResMut<Date>) {
-    // Simulation day-months per one second of real time
-    const SCALE: f32 = 1.0 / 60.0;
-
-    let delta = time.delta_seconds();
-    let duration = delta * SCALE;
-    date.0.advance(duration);
-}
-
-fn update_date_display(mut display: Query<&mut Text, With<DateDisplay>>, date: Res<Date>) {
-    let mut display = display.single_mut();
-    display.sections[0].value = date.0.to_string();
-}
-
-#[derive(Component)]
-struct DateDisplay;
index 0556e54..6edaa37 100644
--- a/src/sun.rs
+++ b/src/sun.rs
@@ -1,4 +1,4 @@
-use crate::Date;
+use crate::date::Date;
=use bevy::prelude::*;
=use std::f32::consts::PI;
=use std::ops::{Mul, Sub};

Make the ground material (grass) more rough

On by Tad Lispy

It looked too glossy, esp during sunrises and sunsets.

index c600def..4cff36a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -41,7 +41,11 @@ fn setup_ground(
=    const SIZE: f32 = 100.;
=    commands.spawn(PbrBundle {
=        mesh: meshes.add(Circle::new(SIZE)),
-        material: materials.add(Color::hsl(150.0, 0.3, 0.3)),
+        material: materials.add(StandardMaterial {
+            base_color: Color::hsl(150.0, 0.3, 0.3),
+            perceptual_roughness: 0.8,
+            ..default()
+        }),
=        transform: Transform::from_rotation(Quat::from_rotation_x(-FRAC_PI_2)),
=        ..default()
=    });

Implement buildings plugin

On by Tad Lispy

Building a new building every month.

The date plugin will emit NewMonth events on the turn of every month. Every time the new buildings plugin will read such an events it will spawn a new building.

Time is sped up, so we don't have to wait too long for the new buildings to show.

new file mode 100644
index 0000000..4571974
--- /dev/null
+++ b/src/buildings.rs
@@ -0,0 +1,48 @@
+use crate::date::NewMonth;
+use bevy::prelude::*;
+
+pub struct BuildingsPlugin;
+
+impl Plugin for BuildingsPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(Startup, setup_sculpture)
+            .add_systems(Update, build);
+    }
+}
+
+#[derive(Component)]
+struct Building;
+
+fn build(
+    mut commands: Commands,
+    assets: ResMut<AssetServer>,
+    mut new_month: EventReader<NewMonth>,
+    buildings: Query<&Building>,
+) {
+    if new_month.read().count() == 0 {
+        return;
+    }
+
+    info!("New month - new building!");
+
+    let model = assets.load("large_buildingB.glb#Scene0");
+    let count = buildings.into_iter().count();
+
+    commands
+        .spawn(SceneBundle {
+            scene: model,
+            transform: Transform::from_xyz(count as f32 * 2., 0., 0.),
+            ..default()
+        })
+        .insert(Building);
+}
+
+fn setup_sculpture(mut commands: Commands, assets: ResMut<AssetServer>) {
+    let model = assets.load("suzanne.glb#Scene0");
+
+    commands.spawn(SceneBundle {
+        scene: model,
+        transform: Transform::from_xyz(3., 0., 3.),
+        ..default()
+    });
+}
index e465f96..96bbdfe 100644
--- a/src/date.rs
+++ b/src/date.rs
@@ -1,7 +1,8 @@
=use crate::day_month::DayMonth;
=use bevy::prelude::*;
=
-const SCALE: f32 = 1.0 / 60.0;
+/// Simulation day-months per one second of real time
+const SCALE: f32 = 1.0 / 3.0;
=
=pub struct DatePlugin {
=    pub start: i32,
@@ -10,6 +11,7 @@ pub struct DatePlugin {
=impl Plugin for DatePlugin {
=    fn build(&self, app: &mut App) {
=        app.insert_resource(Date(DayMonth::new(self.start)))
+            .add_event::<NewMonth>()
=            .add_systems(Startup, setup_date_display)
=            .add_systems(Update, (advance_date, update_date_display));
=    }
@@ -18,12 +20,20 @@ impl Plugin for DatePlugin {
=#[derive(Resource)]
=pub struct Date(pub DayMonth);
=
-fn advance_date(time: Res<Time>, mut date: ResMut<Date>) {
-    // Simulation day-months per one second of real time
+#[derive(Event)]
+pub struct NewMonth;
+
+fn advance_date(time: Res<Time>, mut date: ResMut<Date>, mut events: EventWriter<NewMonth>) {
+    let month = date.0.month();
=
=    let delta = time.delta_seconds();
=    let duration = delta * SCALE;
=    date.0.advance(duration);
+
+    if date.0.month() != month {
+        events.send(NewMonth);
+        info!("It's a new month: {month}");
+    }
=}
=
=// TODO: Consider if date display logic should go to a UI module?
index 4cff36a..49869dd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,11 @@
+mod buildings;
=mod camera;
=mod date;
=mod day_month;
=mod sun;
=
=use bevy::prelude::*;
+use buildings::BuildingsPlugin;
=use date::DatePlugin;
=use std::f32::consts::FRAC_PI_2;
=
@@ -23,9 +25,10 @@ fn main() {
=            distance: 12.,
=            rps: 0.01,
=        }))
+        .add_plugins(BuildingsPlugin)
=        .add_plugins(DatePlugin { start: 1850 })
=        .add_plugins(sun::SunPlugin)
-        .add_systems(Startup, (greet, setup_ground, setup_house, setup_sculpture))
+        .add_systems(Startup, (greet, setup_ground))
=        .run()
=}
=
@@ -50,23 +53,3 @@ fn setup_ground(
=        ..default()
=    });
=}
-
-fn setup_house(mut commands: Commands, assets: ResMut<AssetServer>) {
-    let model = assets.load("large_buildingB.glb#Scene0");
-
-    commands.spawn(SceneBundle {
-        scene: model,
-        transform: Transform::from_xyz(0., 0., 0.),
-        ..default()
-    });
-}
-
-fn setup_sculpture(mut commands: Commands, assets: ResMut<AssetServer>) {
-    let model = assets.load("suzanne.glb#Scene0");
-
-    commands.spawn(SceneBundle {
-        scene: model,
-        transform: Transform::from_xyz(3., 0., 3.),
-        ..default()
-    });
-}

Let the houses stand in rows of 6

On by Tad Lispy

Just so it looks a bit more like a city :P

index 4571974..9f08152 100644
--- a/src/buildings.rs
+++ b/src/buildings.rs
@@ -23,15 +23,20 @@ fn build(
=        return;
=    }
=
-    info!("New month - new building!");
-
=    let model = assets.load("large_buildingB.glb#Scene0");
=    let count = buildings.into_iter().count();
=
+    const ROWS: usize = 6;
+    const DISTANCE: usize = 2;
+    let x = (count.rem_euclid(ROWS) * DISTANCE) as f32;
+    let z = (count / ROWS * DISTANCE) as f32;
+
+    info!("New month - new building at ({x:.2}, {z:.2})!");
+
=    commands
=        .spawn(SceneBundle {
=            scene: model,
-            transform: Transform::from_xyz(count as f32 * 2., 0., 0.),
+            transform: Transform::from_xyz(x, 0., z),
=            ..default()
=        })
=        .insert(Building);

Prevent negative luminosity at night

On by Tad Lispy

The night sun was emitting negative light from under the ground :O

index 6edaa37..7d25d8c 100644
--- a/src/sun.rs
+++ b/src/sun.rs
@@ -59,7 +59,7 @@ fn update_sunlight_color(mut sun: Query<&mut DirectionalLight, With<Sun>>, date:
=    let mut sun = sun.single_mut();
=    sun.color.set_h(hue);
=    sun.color.set_l(lightness);
-    sun.illuminance = illuminance;
+    sun.illuminance = illuminance.max(0.0);
=}
=
=// TODO: Add ambient light. As it is, at noon shadows are too dark.

Prevent initial flash of white before game loads

On by Tad Lispy

Make the game canvas' background dark gray. Easier on eyes.

index d11a782..e73b292 100644
--- a/index.html
+++ b/index.html
@@ -26,6 +26,7 @@
=         #game-canvas {
=             width: 100%;
=             height: 100%;
+             background: hsl(0, 0%, 10%);
=         }
=        </style>
=        <script type="text/javascript">

Separate ground logic to a plugin

On by Tad Lispy

new file mode 100644
index 0000000..f2a6489
--- /dev/null
+++ b/src/ground.rs
@@ -0,0 +1,33 @@
+use bevy::prelude::*;
+use std::f32::consts::FRAC_PI_2;
+
+pub struct GroundPlugin;
+
+impl Plugin for GroundPlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(Startup, setup_ground);
+    }
+}
+
+#[derive(Component)]
+struct Ground;
+
+fn setup_ground(
+    mut commands: Commands,
+    mut meshes: ResMut<Assets<Mesh>>,
+    mut materials: ResMut<Assets<StandardMaterial>>,
+) {
+    const SIZE: f32 = 100.;
+    commands
+        .spawn(PbrBundle {
+            mesh: meshes.add(Circle::new(SIZE)),
+            material: materials.add(StandardMaterial {
+                base_color: Color::hsl(150.0, 0.3, 0.3),
+                perceptual_roughness: 0.8,
+                ..default()
+            }),
+            transform: Transform::from_rotation(Quat::from_rotation_x(-FRAC_PI_2)),
+            ..default()
+        })
+        .insert(Ground);
+}
index 49869dd..92e8ebd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,12 +2,13 @@ mod buildings;
=mod camera;
=mod date;
=mod day_month;
+mod ground;
=mod sun;
=
=use bevy::prelude::*;
=use buildings::BuildingsPlugin;
=use date::DatePlugin;
-use std::f32::consts::FRAC_PI_2;
+use ground::GroundPlugin;
=
=fn main() {
=    App::new()
@@ -25,10 +26,11 @@ fn main() {
=            distance: 12.,
=            rps: 0.01,
=        }))
+        .add_plugins(GroundPlugin)
=        .add_plugins(BuildingsPlugin)
=        .add_plugins(DatePlugin { start: 1850 })
=        .add_plugins(sun::SunPlugin)
-        .add_systems(Startup, (greet, setup_ground))
+        .add_systems(Startup, greet)
=        .run()
=}
=
@@ -36,20 +38,4 @@ fn greet() {
=    info!("Let's build the city on rock and roll!")
=}
=
-fn setup_ground(
-    mut commands: Commands,
-    mut meshes: ResMut<Assets<Mesh>>,
-    mut materials: ResMut<Assets<StandardMaterial>>,
-) {
-    const SIZE: f32 = 100.;
-    commands.spawn(PbrBundle {
-        mesh: meshes.add(Circle::new(SIZE)),
-        material: materials.add(StandardMaterial {
-            base_color: Color::hsl(150.0, 0.3, 0.3),
-            perceptual_roughness: 0.8,
-            ..default()
-        }),
-        transform: Transform::from_rotation(Quat::from_rotation_x(-FRAC_PI_2)),
-        ..default()
-    });
=}

Code consistency: use unqualified SunPlugin

On by Tad Lispy

Just so it's consistent with other plugins.

index 92e8ebd..fc88b87 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,6 +9,7 @@ use bevy::prelude::*;
=use buildings::BuildingsPlugin;
=use date::DatePlugin;
=use ground::GroundPlugin;
+use sun::SunPlugin;
=
=fn main() {
=    App::new()
@@ -29,7 +30,7 @@ fn main() {
=        .add_plugins(GroundPlugin)
=        .add_plugins(BuildingsPlugin)
=        .add_plugins(DatePlugin { start: 1850 })
-        .add_plugins(sun::SunPlugin)
+        .add_plugins(SunPlugin)
=        .add_systems(Startup, greet)
=        .run()
=}

Introduce game states: Simulate and Explore

On by Tad Lispy

The program starts in Simulate state and switches to Explore after a year. For now the state doesn't influence any systems, but it will.

index 76c684e..135daa4 100644
--- a/src/day_month.rs
+++ b/src/day_month.rs
@@ -74,47 +74,50 @@ mod month_tests {
=}
=
=/// Represents a month in a day
+#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
=pub struct DayMonth {
-    /// How many daymonths have passed since the start
-    value: f32,
-    /// The first year when the city was founded
-    start: i32,
+    /// The current year
+    year: i32,
+    /// How many daymonths have passed since the start of the year.
+    ///
+    /// From 0.0 (inclusive) to 12.0 (exclusive).
+    month: f32,
=}
=
=impl DayMonth {
=    /// Initialize the day-month. Returns beginning (midnight) of the January of a given start year.
=    pub fn new(year: i32) -> Self {
-        Self {
-            start: year,
-            value: 0.0,
-        }
+        Self { year, month: 0.0 }
=    }
=
=    pub fn advance(&mut self, duration: f32) -> &Self {
-        self.value += duration;
+        let month = self.month + duration;
+
+        self.month = month.rem_euclid(12.0);
+        self.year += month.div_euclid(12.0).floor() as i32;
=        self
=    }
=
=    pub fn year(&self) -> i32 {
-        self.value.div_euclid(12.0) as i32 + self.start
+        self.year
=    }
=
=    pub fn month(&self) -> Month {
-        Month::new(self.value as i32 + 1)
+        Month::new(self.month as i32 + 1)
=    }
=
=    pub fn hour(&self) -> i32 {
-        (self.value.rem_euclid(1.0) * 24.0) as i32
+        (self.month.rem_euclid(1.0) * 24.0) as i32
=    }
=
=    pub fn minute(&self) -> f32 {
=        // TODO: Can minutes calculation be simplified for efficiency?
-        (self.value.rem_euclid(1.0) * 24.0).rem_euclid(1.0) * 60.0
+        (self.month.rem_euclid(1.0) * 24.0).rem_euclid(1.0) * 60.0
=    }
=
=    /// Return a number between 0.0 and 1.0 representing the fraction of the day that passed
=    pub fn time_of_day(&self) -> f32 {
-        self.value.rem_euclid(1.0)
+        self.month.rem_euclid(1.0)
=    }
=}
=
@@ -169,4 +172,15 @@ mod daymonth_tests {
=        daymonth.advance(12. * HOUR);
=        assert_eq!(format!("{daymonth}"), "April 1984 01:45");
=    }
+
+    #[test]
+    fn order_and_compare() {
+        assert_eq!(DayMonth::new(2000), DayMonth::new(2000));
+
+        assert!(DayMonth::new(2000) < DayMonth::new(2001));
+        assert!(DayMonth::new(2001) > DayMonth::new(2000));
+        assert!(DayMonth::new(2000) < DayMonth::new(2000).advance(1.0).to_owned());
+        assert!(DayMonth::new(2001) < DayMonth::new(2000).advance(12.5).to_owned());
+        assert!(DayMonth::new(2000) > DayMonth::new(2000).advance(-0.1).to_owned());
+    }
=}
index fc88b87..d64a204 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,7 +7,10 @@ mod sun;
=
=use bevy::prelude::*;
=use buildings::BuildingsPlugin;
+use date::Date;
=use date::DatePlugin;
+use date::NewMonth;
+use day_month::DayMonth;
=use ground::GroundPlugin;
=use sun::SunPlugin;
=
@@ -22,6 +25,7 @@ fn main() {
=            }),
=            ..default()
=        }))
+        .init_state::<GameState>()
=        .add_plugins(camera::CameraPlugin(camera::CameraSettings {
=            elevation: 5.,
=            distance: 12.,
@@ -32,6 +36,8 @@ fn main() {
=        .add_plugins(DatePlugin { start: 1850 })
=        .add_plugins(SunPlugin)
=        .add_systems(Startup, greet)
+        .add_systems(Update, switch_states)
+        .add_systems(OnEnter(GameState::Explore), setup_exploration)
=        .run()
=}
=
@@ -39,4 +45,27 @@ fn greet() {
=    info!("Let's build the city on rock and roll!")
=}
=
+#[derive(States, Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
+enum GameState {
+    #[default]
+    Simulate,
+    Explore,
+}
+
+fn switch_states(
+    mut new_month_events: EventReader<NewMonth>,
+    date: Res<Date>,
+    mut state: ResMut<NextState<GameState>>,
+) {
+    if new_month_events.read().count() == 0 {
+        return;
+    }
+
+    if date.0 > DayMonth::new(1851) {
+        state.set(GameState::Explore)
+    }
+}
+
+fn setup_exploration() {
+    info!("Now exploring!")
=}

Decouple construction orders from construction

On by Tad Lispy

Construction order is a decision to construct a new house. They are only issued in the Simulate state. The spawning of a new building follows the construction order. The goal is to have a history system that will store the orders in Simulate state and re-issue them in Explore state.

Technically ConstructionOrder is an event.

I removed the logic related to the little sculpture. It would need to be updated, but it's not worth the effort. I only added it to get a hand on working with GLTF models in the first place.

index 9f08152..4c6767a 100644
--- a/src/buildings.rs
+++ b/src/buildings.rs
@@ -1,29 +1,43 @@
=use crate::date::NewMonth;
+use crate::GameState;
=use bevy::prelude::*;
=
=pub struct BuildingsPlugin;
=
=impl Plugin for BuildingsPlugin {
=    fn build(&self, app: &mut App) {
-        app.add_systems(Startup, setup_sculpture)
-            .add_systems(Update, build);
+        app.add_event::<ConstructionOrder>()
+            .add_systems(
+                Update,
+                order_construction.run_if(in_state(GameState::Simulate)),
+            )
+            .add_systems(Update, spawn_buildings);
=    }
=}
=
+/// This event represents a decision to construct a new building.
+///
+/// It will be stored in the history, and will result in spawning a new building.
+#[derive(Event, Clone, Copy)]
+pub struct ConstructionOrder {
+    pub x: f32,
+    pub z: f32,
+}
+
+/// A tag for building entities
=#[derive(Component)]
=struct Building;
=
-fn build(
-    mut commands: Commands,
-    assets: ResMut<AssetServer>,
-    mut new_month: EventReader<NewMonth>,
+/// Issue a construction order, for the record and effect
+fn order_construction(
+    mut new_month_events: EventReader<NewMonth>,
+    mut build_events: EventWriter<ConstructionOrder>,
=    buildings: Query<&Building>,
=) {
-    if new_month.read().count() == 0 {
+    if new_month_events.read().count() == 0 {
=        return;
=    }
=
-    let model = assets.load("large_buildingB.glb#Scene0");
=    let count = buildings.into_iter().count();
=
=    const ROWS: usize = 6;
@@ -31,23 +45,28 @@ fn build(
=    let x = (count.rem_euclid(ROWS) * DISTANCE) as f32;
=    let z = (count / ROWS * DISTANCE) as f32;
=
-    info!("New month - new building at ({x:.2}, {z:.2})!");
+    info!("Ordering a new construction at ({x:.2}, {z:.2})!");
=
-    commands
-        .spawn(SceneBundle {
-            scene: model,
-            transform: Transform::from_xyz(x, 0., z),
-            ..default()
-        })
-        .insert(Building);
+    build_events.send(ConstructionOrder { x, z });
=}
=
-fn setup_sculpture(mut commands: Commands, assets: ResMut<AssetServer>) {
-    let model = assets.load("suzanne.glb#Scene0");
+/// Implement the construction orders
+fn spawn_buildings(
+    mut commands: Commands,
+    assets: ResMut<AssetServer>,
+    mut construction_orders: EventReader<ConstructionOrder>,
+) {
+    for ConstructionOrder { x, z } in construction_orders.read() {
+        let model = assets.load("large_buildingB.glb#Scene0");
=
-    commands.spawn(SceneBundle {
-        scene: model,
-        transform: Transform::from_xyz(3., 0., 3.),
-        ..default()
-    });
+        info!("Spawning a new building at ({x:.2}, {z:.2})!");
+
+        commands
+            .spawn(SceneBundle {
+                scene: model,
+                transform: Transform::from_xyz(*x, 0., *z),
+                ..default()
+            })
+            .insert(Building);
+    }
=}

Update dependencies (Nix and Cargo)

On by Tad Lispy

Mostly I want a new rust-analyzer, as the current version is affected by https://github.com/rust-lang/rust-analyzer/issues/16614. But it's a good hygiene to upgrade the rest too.

index be9d0ed..599fc8b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -80,9 +80,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
=
=[[package]]
=name = "ahash"
-version = "0.8.9"
+version = "0.8.10"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f"
+checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b"
=dependencies = [
= "cfg-if",
= "getrandom",
@@ -223,7 +223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
=dependencies = [
= "concurrent-queue",
- "event-listener 5.1.0",
+ "event-listener 5.2.0",
= "event-listener-strategy 0.5.0",
= "futures-core",
= "pin-project-lite",
@@ -386,7 +386,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -452,7 +452,7 @@ checksum = "028ae2a34678055185d7f1beebb1ebe6a8dcf3733e139e4ee1383a7f29ae8ba6"
=dependencies = [
= "bevy_macro_utils",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -500,7 +500,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -560,7 +560,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -686,7 +686,7 @@ dependencies = [
= "proc-macro2",
= "quote",
= "rustc-hash",
- "syn 2.0.51",
+ "syn 2.0.52",
= "toml_edit 0.21.1",
=]
=
@@ -767,7 +767,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
= "uuid",
=]
=
@@ -825,7 +825,7 @@ dependencies = [
= "bevy_macro_utils",
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -993,7 +993,7 @@ checksum = "31ae98e9c0c08b0f5c90e22cd713201f759b98d4fd570b99867a695f8641859a"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -1055,7 +1055,7 @@ dependencies = [
= "regex",
= "rustc-hash",
= "shlex",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -1184,7 +1184,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -1483,9 +1483,9 @@ dependencies = [
=
=[[package]]
=name = "crossbeam-channel"
-version = "0.5.11"
+version = "0.5.12"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
=dependencies = [
= "crossbeam-utils",
=]
@@ -1594,7 +1594,7 @@ checksum = "92959a9e8d13eaa13b8ae8c7b583c3bf1669ca7a8e7708a088d12587ba86effc"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -1650,9 +1650,9 @@ dependencies = [
=
=[[package]]
=name = "event-listener"
-version = "5.1.0"
+version = "5.2.0"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27"
+checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
=dependencies = [
= "concurrent-queue",
= "parking",
@@ -1675,7 +1675,7 @@ version = "0.5.0"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
=dependencies = [
- "event-listener 5.1.0",
+ "event-listener 5.2.0",
= "pin-project-lite",
=]
=
@@ -1734,7 +1734,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -1885,7 +1885,7 @@ dependencies = [
= "inflections",
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -2056,9 +2056,9 @@ dependencies = [
=
=[[package]]
=name = "indexmap"
-version = "2.2.3"
+version = "2.2.5"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
=dependencies = [
= "equivalent",
= "hashbrown",
@@ -2288,9 +2288,9 @@ dependencies = [
=
=[[package]]
=name = "log"
-version = "0.4.20"
+version = "0.4.21"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
=
=[[package]]
=name = "mach2"
@@ -2367,9 +2367,9 @@ dependencies = [
=
=[[package]]
=name = "naga"
-version = "0.19.0"
+version = "0.19.2"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8878eb410fc90853da3908aebfe61d73d26d4437ef850b70050461f939509899"
+checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843"
=dependencies = [
= "bit-set",
= "bitflags 2.4.2",
@@ -2575,7 +2575,7 @@ dependencies = [
= "proc-macro-crate 3.1.0",
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -3145,7 +3145,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -3281,9 +3281,9 @@ dependencies = [
=
=[[package]]
=name = "syn"
-version = "2.0.51"
+version = "2.0.52"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
+checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
=dependencies = [
= "proc-macro2",
= "quote",
@@ -3342,7 +3342,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -3442,7 +3442,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
=
=[[package]]
@@ -3611,7 +3611,7 @@ dependencies = [
= "once_cell",
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
= "wasm-bindgen-shared",
=]
=
@@ -3645,7 +3645,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
= "wasm-bindgen-backend",
= "wasm-bindgen-shared",
=]
@@ -3786,9 +3786,9 @@ dependencies = [
=
=[[package]]
=name = "wgpu"
-version = "0.19.1"
+version = "0.19.2"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bfe9a310dcf2e6b85f00c46059aaeaf4184caa8e29a1ecd4b7a704c3482332d"
+checksum = "f9274d073bfb0cad6c53c575d3f4815617c9ccc134cba3ead1fa5fa51e71595c"
=dependencies = [
= "arrayvec",
= "cfg-if",
@@ -3811,9 +3811,9 @@ dependencies = [
=
=[[package]]
=name = "wgpu-core"
-version = "0.19.0"
+version = "0.19.2"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b15e451d4060ada0d99a64df44e4d590213496da7c4f245572d51071e8e30ed"
+checksum = "0c8bb6a5c62ad78bd683609d525cde6efb6e7cf39e008ea0b8e42518ce18ea81"
=dependencies = [
= "arrayvec",
= "bit-vec",
@@ -3837,9 +3837,9 @@ dependencies = [
=
=[[package]]
=name = "wgpu-hal"
-version = "0.19.1"
+version = "0.19.2"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bb47856236bfafc0bc591a925eb036ac19cd987624a447ff353e7a7e7e6f72"
+checksum = "cb7b9a56d44851cc0f51bf2f8b5ed8af5cf6e4bdb178fd5786ea2b4771e2edf0"
=dependencies = [
= "android_system_properties",
= "arrayvec",
@@ -3881,9 +3881,9 @@ dependencies = [
=
=[[package]]
=name = "wgpu-types"
-version = "0.19.0"
+version = "0.19.2"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "895fcbeb772bfb049eb80b2d6e47f6c9af235284e9703c96fc0218a42ffd5af2"
+checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805"
=dependencies = [
= "bitflags 2.4.2",
= "js-sys",
@@ -3954,7 +3954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
=dependencies = [
= "windows-core",
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
=]
=
=[[package]]
@@ -3963,7 +3963,7 @@ version = "0.52.0"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
=dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
=]
=
=[[package]]
@@ -4012,7 +4012,7 @@ version = "0.52.0"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
=dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
=]
=
=[[package]]
@@ -4047,17 +4047,17 @@ dependencies = [
=
=[[package]]
=name = "windows-targets"
-version = "0.52.3"
+version = "0.52.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
=dependencies = [
- "windows_aarch64_gnullvm 0.52.3",
- "windows_aarch64_msvc 0.52.3",
- "windows_i686_gnu 0.52.3",
- "windows_i686_msvc 0.52.3",
- "windows_x86_64_gnu 0.52.3",
- "windows_x86_64_gnullvm 0.52.3",
- "windows_x86_64_msvc 0.52.3",
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
=]
=
=[[package]]
@@ -4074,9 +4074,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
=
=[[package]]
=name = "windows_aarch64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
=
=[[package]]
=name = "windows_aarch64_msvc"
@@ -4092,9 +4092,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
=
=[[package]]
=name = "windows_aarch64_msvc"
-version = "0.52.3"
+version = "0.52.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
=
=[[package]]
=name = "windows_i686_gnu"
@@ -4110,9 +4110,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
=
=[[package]]
=name = "windows_i686_gnu"
-version = "0.52.3"
+version = "0.52.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
=
=[[package]]
=name = "windows_i686_msvc"
@@ -4128,9 +4128,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
=
=[[package]]
=name = "windows_i686_msvc"
-version = "0.52.3"
+version = "0.52.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
=
=[[package]]
=name = "windows_x86_64_gnu"
@@ -4146,9 +4146,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
=
=[[package]]
=name = "windows_x86_64_gnu"
-version = "0.52.3"
+version = "0.52.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
=
=[[package]]
=name = "windows_x86_64_gnullvm"
@@ -4164,9 +4164,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
=
=[[package]]
=name = "windows_x86_64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
=
=[[package]]
=name = "windows_x86_64_msvc"
@@ -4182,9 +4182,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
=
=[[package]]
=name = "windows_x86_64_msvc"
-version = "0.52.3"
+version = "0.52.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
=
=[[package]]
=name = "winit"
@@ -4329,5 +4329,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
=dependencies = [
= "proc-macro2",
= "quote",
- "syn 2.0.51",
+ "syn 2.0.52",
=]
index e352084..e4f7bad 100644
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
=        "systems": "systems"
=      },
=      "locked": {
-        "lastModified": 1705309234,
-        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
+        "lastModified": 1709126324,
+        "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
=        "owner": "numtide",
=        "repo": "flake-utils",
-        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
+        "rev": "d465f4819400de7c8d874d50b982301f28a84605",
=        "type": "github"
=      },
=      "original": {
@@ -38,11 +38,11 @@
=    },
=    "nixpkgs": {
=      "locked": {
-        "lastModified": 1708807242,
-        "narHash": "sha256-sRTRkhMD4delO/hPxxi+XwLqPn8BuUq6nnj4JqLwOu0=",
+        "lastModified": 1709150264,
+        "narHash": "sha256-HofykKuisObPUfj0E9CJVfaMhawXkYx3G8UIFR/XQ38=",
=        "owner": "NixOS",
=        "repo": "nixpkgs",
-        "rev": "73de017ef2d18a04ac4bfd0c02650007ccb31c2a",
+        "rev": "9099616b93301d5cf84274b184a3a5ec69e94e08",
=        "type": "github"
=      },
=      "original": {
@@ -81,11 +81,11 @@
=        "nixpkgs": "nixpkgs_2"
=      },
=      "locked": {
-        "lastModified": 1708913568,
-        "narHash": "sha256-76PGANC2ADf0h7fe0w2nWpfdGN+bemFs2rvW2EdU/ZY=",
+        "lastModified": 1709172595,
+        "narHash": "sha256-0oYeE5VkhnPA7YBl+0Utq2cYoHcfsEhSGwraCa27Vs8=",
=        "owner": "oxalica",
=        "repo": "rust-overlay",
-        "rev": "cbdf3e5bb205ff2ca165fe661fbd6d885cbd0106",
+        "rev": "72fa0217f76020ad3aeb2dd9dd72490905b23b6f",
=        "type": "github"
=      },
=      "original": {

The date progression will stop as the sim is done

On by Tad Lispy

On the last day-month of the experiment (currently January 1861) the time slows down to a halt at 09:32.

The beginning and duration of the experiment are constants again. Later probably we will want to place them as a resource in the world. But for now it's just easier.

index 96bbdfe..2087dd2 100644
--- a/src/date.rs
+++ b/src/date.rs
@@ -1,16 +1,15 @@
-use crate::day_month::DayMonth;
+use crate::day_month::{DayMonth, HOUR, MINUTE};
+use crate::{GameState, BEGINNING, DURATION};
=use bevy::prelude::*;
=
=/// Simulation day-months per one second of real time
=const SCALE: f32 = 1.0 / 3.0;
=
-pub struct DatePlugin {
-    pub start: i32,
-}
+pub struct DatePlugin;
=
=impl Plugin for DatePlugin {
=    fn build(&self, app: &mut App) {
-        app.insert_resource(Date(DayMonth::new(self.start)))
+        app.insert_resource(Date(DayMonth::new(BEGINNING)))
=            .add_event::<NewMonth>()
=            .add_systems(Startup, setup_date_display)
=            .add_systems(Update, (advance_date, update_date_display));
@@ -24,10 +23,18 @@ pub struct Date(pub DayMonth);
=pub struct NewMonth;
=
=fn advance_date(time: Res<Time>, mut date: ResMut<Date>, mut events: EventWriter<NewMonth>) {
+    // Do not go much over the duration, but gently slow down after
+    const ENDTIME: f32 = (9.0 * HOUR + 32.0 * MINUTE);
+    let scale = if date.0.year() < (BEGINNING + DURATION) {
+        SCALE
+    } else {
+        SCALE * (ENDTIME - date.0.time_of_day()).max(0.0)
+    };
+
=    let month = date.0.month();
=
=    let delta = time.delta_seconds();
-    let duration = delta * SCALE;
+    let duration = delta * scale;
=    date.0.advance(duration);
=
=    if date.0.month() != month {
index d64a204..134d4b0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,6 +14,9 @@ use day_month::DayMonth;
=use ground::GroundPlugin;
=use sun::SunPlugin;
=
+const BEGINNING: i32 = 1860;
+const DURATION: i32 = 1;
+
=fn main() {
=    App::new()
=        .add_plugins(DefaultPlugins.set(WindowPlugin {
@@ -33,7 +36,7 @@ fn main() {
=        }))
=        .add_plugins(GroundPlugin)
=        .add_plugins(BuildingsPlugin)
-        .add_plugins(DatePlugin { start: 1850 })
+        .add_plugins(DatePlugin)
=        .add_plugins(SunPlugin)
=        .add_systems(Startup, greet)
=        .add_systems(Update, switch_states)
@@ -61,7 +64,7 @@ fn switch_states(
=        return;
=    }
=
-    if date.0 > DayMonth::new(1851) {
+    if date.0 > DayMonth::new(BEGINNING + DURATION) {
=        state.set(GameState::Explore)
=    }
=}

Implement POC snapshots and rollback (time travel)

On by Tad Lispy

The events are stored, but not played back yet.

index 599fc8b..4c1f6cb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -179,6 +179,25 @@ dependencies = [
= "num-traits",
=]
=
+[[package]]
+name = "arboard"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1faa3c733d9a3dd6fbaf85da5d162a2e03b2e0033a90dceb0e2a90fdd1e5380a"
+dependencies = [
+ "clipboard-win",
+ "core-graphics",
+ "image",
+ "log",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "parking_lot",
+ "thiserror",
+ "windows-sys 0.48.0",
+ "x11rb",
+]
+
=[[package]]
=name = "arrayref"
=version = "0.3.7"
@@ -503,6 +522,20 @@ dependencies = [
= "syn 2.0.52",
=]
=
+[[package]]
+name = "bevy_egui"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b84bfb8d4104a1467910cf2090bc6a6d394ebde39c0dbc02397b45aa9ef88e80"
+dependencies = [
+ "arboard",
+ "bevy",
+ "egui",
+ "thread_local",
+ "web-sys",
+ "webbrowser",
+]
+
=[[package]]
=name = "bevy_encase_derive"
=version = "0.13.0"
@@ -1272,6 +1305,15 @@ dependencies = [
= "libloading 0.8.1",
=]
=
+[[package]]
+name = "clipboard-win"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f9a0700e0127ba15d1d52dd742097f821cd9c65939303a44d970465040a297"
+dependencies = [
+ "error-code",
+]
+
=[[package]]
=name = "codespan-reporting"
=version = "0.11.1"
@@ -1559,12 +1601,41 @@ version = "1.2.0"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
=
+[[package]]
+name = "ecolor"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03cfe80b1890e1a8cdbffc6044d6872e814aaf6011835a2a5e2db0e5c5c4ef4e"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "egui"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180f595432a5b615fc6b74afef3955249b86cfea72607b40740a4cd60d5297d0"
+dependencies = [
+ "ahash",
+ "epaint",
+ "nohash-hasher",
+]
+
=[[package]]
=name = "either"
=version = "1.10.0"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
=
+[[package]]
+name = "emath"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6916301ecf80448f786cdf3eb51d9dbdd831538732229d49119e2d4312eaaf09"
+dependencies = [
+ "bytemuck",
+]
+
=[[package]]
=name = "encase"
=version = "0.7.0"
@@ -1597,6 +1668,21 @@ dependencies = [
= "syn 2.0.52",
=]
=
+[[package]]
+name = "epaint"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77b9fdf617dd7f58b0c8e6e9e4a1281f730cde0831d40547da446b2bb76a47af"
+dependencies = [
+ "ab_glyph",
+ "ahash",
+ "bytemuck",
+ "ecolor",
+ "emath",
+ "nohash-hasher",
+ "parking_lot",
+]
+
=[[package]]
=name = "equivalent"
=version = "1.0.1"
@@ -1622,6 +1708,12 @@ dependencies = [
= "windows-sys 0.52.0",
=]
=
+[[package]]
+name = "error-code"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
+
=[[package]]
=name = "euclid"
=version = "0.22.9"
@@ -1743,6 +1835,15 @@ version = "0.3.1"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
=
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
=[[package]]
=name = "futures-core"
=version = "0.3.30"
@@ -2030,6 +2131,15 @@ version = "0.2.1"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
=
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
=[[package]]
=name = "icrate"
=version = "0.0.4"
@@ -2041,6 +2151,16 @@ dependencies = [
= "objc2 0.4.1",
=]
=
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
=[[package]]
=name = "image"
=version = "0.24.9"
@@ -2052,6 +2172,7 @@ dependencies = [
= "color_quant",
= "num-traits",
= "png",
+ "tiff",
=]
=
=[[package]]
@@ -2165,6 +2286,12 @@ version = "0.3.0"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
=
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
+
=[[package]]
=name = "js-sys"
=version = "0.3.68"
@@ -2481,6 +2608,12 @@ dependencies = [
= "libc",
=]
=
+[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
=[[package]]
=name = "nom"
=version = "7.1.3"
@@ -2588,6 +2721,17 @@ dependencies = [
= "objc_exception",
=]
=
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
=[[package]]
=name = "objc-sys"
=version = "0.2.0-beta.2"
@@ -2645,6 +2789,15 @@ dependencies = [
= "cc",
=]
=
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
=[[package]]
=name = "oboe"
=version = "0.5.0"
@@ -2697,6 +2850,7 @@ name = "otterhide"
=version = "0.1.0"
=dependencies = [
= "bevy",
+ "bevy_egui",
= "derive_more",
= "itertools",
= "js-sys",
@@ -3355,6 +3509,17 @@ dependencies = [
= "once_cell",
=]
=
+[[package]]
+name = "tiff"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
+dependencies = [
+ "flate2",
+ "jpeg-decoder",
+ "weezl",
+]
+
=[[package]]
=name = "tiny-skia"
=version = "0.11.4"
@@ -3522,12 +3687,27 @@ dependencies = [
= "static_assertions",
=]
=
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
=[[package]]
=name = "unicode-ident"
=version = "1.0.12"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
=
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
=[[package]]
=name = "unicode-segmentation"
=version = "1.11.0"
@@ -3546,6 +3726,17 @@ version = "0.2.4"
=source = "registry+https://github.com/rust-lang/crates.io-index"
=checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
=
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
=[[package]]
=name = "uuid"
=version = "1.7.0"
@@ -3784,6 +3975,29 @@ dependencies = [
= "wasm-bindgen",
=]
=
+[[package]]
+name = "webbrowser"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71"
+dependencies = [
+ "core-foundation",
+ "home",
+ "jni 0.21.1",
+ "log",
+ "ndk-context",
+ "objc",
+ "raw-window-handle 0.5.2",
+ "url",
+ "web-sys",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
+
=[[package]]
=name = "wgpu"
=version = "0.19.2"
index 82a0963..6670181 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2021"
=
=[dependencies]
=bevy = { version = "0.13", features = ["wayland"] }
+bevy_egui = "0.25.0"
=derive_more = "0.99.17"
=itertools = "0.12.1"
=js-sys = "0.3.68"
index 4c6767a..1abb8c0 100644
--- a/src/buildings.rs
+++ b/src/buildings.rs
@@ -1,4 +1,5 @@
=use crate::date::NewMonth;
+use crate::history::TimeTravelRequest;
=use crate::GameState;
=use bevy::prelude::*;
=
@@ -11,7 +12,8 @@ impl Plugin for BuildingsPlugin {
=                Update,
=                order_construction.run_if(in_state(GameState::Simulate)),
=            )
-            .add_systems(Update, spawn_buildings);
+            .add_systems(Update, spawn_buildings)
+            .add_systems(Update, rollback);
=    }
=}
=
@@ -26,7 +28,7 @@ pub struct ConstructionOrder {
=
=/// A tag for building entities
=#[derive(Component)]
-struct Building;
+pub struct Building;
=
=/// Issue a construction order, for the record and effect
=fn order_construction(
@@ -70,3 +72,29 @@ fn spawn_buildings(
=            .insert(Building);
=    }
=}
+
+fn rollback(
+    mut requests: EventReader<TimeTravelRequest>,
+    buildings: Query<Entity, With<Building>>,
+    assets: ResMut<AssetServer>,
+    mut commands: Commands,
+) {
+    for TimeTravelRequest(snapshot) in requests.read() {
+        for entity in buildings.iter() {
+            commands.entity(entity).despawn_recursive();
+        }
+
+        for transform in snapshot.clone().buildings {
+            // TODO: DRY with spawn_buildings
+            let model = assets.load("large_buildingB.glb#Scene0");
+
+            commands
+                .spawn(SceneBundle {
+                    scene: model,
+                    transform: transform.clone(),
+                    ..default()
+                })
+                .insert(Building);
+        }
+    }
+}
index 2087dd2..586e532 100644
--- a/src/date.rs
+++ b/src/date.rs
@@ -1,9 +1,10 @@
=use crate::day_month::{DayMonth, HOUR, MINUTE};
+use crate::history::TimeTravelRequest;
=use crate::{GameState, BEGINNING, DURATION};
=use bevy::prelude::*;
=
=/// Simulation day-months per one second of real time
-const SCALE: f32 = 1.0 / 3.0;
+const SCALE: f32 = 1.0 / 60.0;
=
=pub struct DatePlugin;
=
@@ -12,7 +13,8 @@ impl Plugin for DatePlugin {
=        app.insert_resource(Date(DayMonth::new(BEGINNING)))
=            .add_event::<NewMonth>()
=            .add_systems(Startup, setup_date_display)
-            .add_systems(Update, (advance_date, update_date_display));
+            .add_systems(Update, (advance_date, update_date_display))
+            .add_systems(Update, rollback);
=    }
=}
=
@@ -22,13 +24,22 @@ pub struct Date(pub DayMonth);
=#[derive(Event)]
=pub struct NewMonth;
=
-fn advance_date(time: Res<Time>, mut date: ResMut<Date>, mut events: EventWriter<NewMonth>) {
+fn advance_date(
+    time: Res<Time>,
+    mut date: ResMut<Date>,
+    mut events: EventWriter<NewMonth>,
+    game_state: Res<State<GameState>>,
+) {
=    // Do not go much over the duration, but gently slow down after
-    const ENDTIME: f32 = (9.0 * HOUR + 32.0 * MINUTE);
-    let scale = if date.0.year() < (BEGINNING + DURATION) {
-        SCALE
+    const ENDTIME: f32 = 9.0 * HOUR + 32.0 * MINUTE;
+    let mut scale = if game_state.get() == &GameState::Simulate {
+        SCALE * 120.
=    } else {
-        SCALE * (ENDTIME - date.0.time_of_day()).max(0.0)
+        SCALE
+    };
+
+    if date.0.year() >= (BEGINNING + DURATION) {
+        scale *= (ENDTIME - date.0.time_of_day()).max(0.0)
=    };
=
=    let month = date.0.month();
@@ -71,3 +82,9 @@ fn update_date_display(mut display: Query<&mut Text, With<DateDisplay>>, date: R
=    let mut display = display.single_mut();
=    display.sections[0].value = date.0.to_string();
=}
+
+fn rollback(mut requests: EventReader<TimeTravelRequest>, mut date: ResMut<Date>) {
+    for TimeTravelRequest(snapshot) in requests.read() {
+        date.0 = snapshot.date;
+    }
+}
index 135daa4..a3c7613 100644
--- a/src/day_month.rs
+++ b/src/day_month.rs
@@ -2,8 +2,9 @@ use std::fmt::Display;
=
=pub const HOUR: f32 = 1.0 / 24.0;
=pub const MINUTE: f32 = HOUR / 60.0;
+pub const MINUTES_PER_DAYMONTH: f32 = (60 * 24) as f32;
=
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
=pub enum Month {
=    January = 1,
=    February,
@@ -135,6 +136,63 @@ impl Display for DayMonth {
=    }
=}
=
+// TODO: Ditch the Timestamp type.
+// If we need keys at all, we can use String from a DayMonth. And maybe keys are not needed at all?
+/// Simplified DayMonth that can be used as a key in a hash map
+#[derive(PartialEq, PartialOrd, Ord, Eq, Hash, Clone, Copy)]
+pub struct Timestamp {
+    pub year: i32,
+    pub month: Month,
+    pub hour: i32,
+    pub minute: i32,
+}
+
+impl From<DayMonth> for Timestamp {
+    fn from(day_month: DayMonth) -> Self {
+        Self {
+            year: day_month.year(),
+            month: day_month.month(),
+            hour: day_month.hour(),
+            minute: day_month.minute() as i32,
+        }
+    }
+}
+
+impl From<Timestamp> for DayMonth {
+    fn from(timestamp: Timestamp) -> Self {
+        let minute = (timestamp.hour * 60 + timestamp.minute) as f32;
+
+        Self {
+            year: timestamp.year,
+            month: (timestamp.month as i32 - 1) as f32 + (minute / MINUTES_PER_DAYMONTH),
+        }
+    }
+}
+
+impl From<&Timestamp> for DayMonth {
+    fn from(timestamp: &Timestamp) -> Self {
+        let minute = (timestamp.hour * 60 + timestamp.minute) as f32;
+
+        Self {
+            year: timestamp.year,
+            month: (timestamp.month as i32 - 1) as f32 + (minute / MINUTES_PER_DAYMONTH),
+        }
+    }
+}
+
+impl Display for Timestamp {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{yyyy} {mmmm} {hh:02}:{mm:02}",
+            yyyy = self.year,
+            mmmm = self.month,
+            hh = self.hour,
+            mm = self.minute
+        )
+    }
+}
+
=#[cfg(test)]
=mod daymonth_tests {
=    use super::*;
new file mode 100644
index 0000000..634bf13
--- /dev/null
+++ b/src/explore.rs
@@ -0,0 +1,41 @@
+use crate::history::History;
+use crate::history::TimeTravelRequest;
+use bevy::prelude::*;
+use bevy_egui::egui;
+use bevy_egui::EguiContexts;
+use itertools::Itertools;
+
+pub struct ExplorePlugin;
+
+impl Plugin for ExplorePlugin {
+    fn build(&self, app: &mut App) {
+        app.add_systems(
+            Update, paint_ui, /* .run_if(in_state(GameState::Explore)) */
+        );
+    }
+}
+
+fn paint_ui(
+    mut contexts: EguiContexts,
+    history: Res<History>,
+    mut requests: EventWriter<TimeTravelRequest>,
+) {
+    egui::SidePanel::right("explore_ui").show(contexts.ctx_mut(), |ui| {
+        ui.heading("Explore");
+
+        for (timestamp, snapshot) in history
+            .snapshots
+            .iter()
+            // TODO: Consider using BTreeMap for always sorted snapshots. Or even ditch keys all together and use a vector?
+            .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
+        {
+            let month = timestamp.month.clone();
+            let year = timestamp.year;
+            let button = ui.button(format!("{month} {year}"));
+            if button.clicked() {
+                info!("Time travel to {timestamp}");
+                requests.send(TimeTravelRequest(snapshot.clone()));
+            }
+        }
+    });
+}
new file mode 100644
index 0000000..0c01085
--- /dev/null
+++ b/src/history.rs
@@ -0,0 +1,109 @@
+use crate::buildings::ConstructionOrder;
+use crate::buildings::{self, Building};
+use crate::date::{Date, NewMonth};
+use crate::day_month::{DayMonth, Timestamp};
+use crate::GameState;
+use bevy::prelude::*;
+use bevy::utils::HashMap;
+use std::fmt::Display;
+
+pub struct HistoryPlugin;
+
+impl Plugin for HistoryPlugin {
+    fn build(&self, app: &mut App) {
+        app.init_resource::<History>()
+            .add_event::<TimeTravelRequest>()
+            .add_systems(
+                PreUpdate,
+                take_snapshot.run_if(in_state(GameState::Simulate)),
+            )
+            .add_systems(
+                Update,
+                register_historical_events.run_if(in_state(GameState::Simulate)),
+            )
+            .add_systems(
+                Update,
+                time_travel, /* .run_if(in_state(GameState::Explore)) */
+            );
+    }
+}
+
+#[derive(Resource, Default)]
+pub struct History {
+    pub events: HashMap<Timestamp, Vec<HistoricalEvent>>,
+    pub snapshots: HashMap<Timestamp, Snapshot>,
+}
+
+pub enum HistoricalEvent {
+    ConstructionOrder(buildings::ConstructionOrder),
+}
+
+impl Display for HistoricalEvent {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            HistoricalEvent::ConstructionOrder(ConstructionOrder { x, z }) => {
+                write!(
+                    f,
+                    "construction of a new building ordered at ({x:.2}, {z:.2})",
+                )
+            }
+        }
+    }
+}
+
+fn register_historical_events(
+    mut history: ResMut<History>,
+    date: Res<Date>,
+    mut construction_orders: EventReader<ConstructionOrder>,
+) {
+    let key: Timestamp = date.0.into();
+    let historical_events = history.events.entry(key).or_insert(Vec::new());
+
+    for event in construction_orders.read() {
+        let historical_event = HistoricalEvent::ConstructionOrder(event.to_owned());
+        info!("Registering a historical event: {historical_event}");
+        historical_events.push(historical_event);
+    }
+}
+
+// TODO: Use hankjordan/bevy_save with a custom pipeline
+// See https://github.com/hankjordan/bevy_save/blob/6fe1d55c822ecd075db9a86d383042c9fa07c192/examples/breakout.rs#L614
+#[derive(Clone)]
+pub struct Snapshot {
+    pub date: DayMonth,
+    pub buildings: Vec<Transform>,
+}
+
+fn take_snapshot(
+    mut new_month: EventReader<NewMonth>,
+    date: Res<Date>,
+    mut history: ResMut<History>,
+    buildings: Query<&Transform, With<Building>>,
+) {
+    if new_month.read().count() == 0 {
+        return;
+    }
+
+    let timestamp: Timestamp = date.0.into();
+    let buildings = buildings.iter().map(|transform| *transform).collect();
+    let snapshot = Snapshot {
+        date: date.0,
+        buildings,
+    };
+    history.snapshots.insert(timestamp.clone(), snapshot);
+
+    let count = history.snapshots.len();
+    info!("Registering snapshot {count} on {timestamp}.")
+}
+
+#[derive(Event)]
+pub struct TimeTravelRequest(pub Snapshot);
+
+fn time_travel(
+    mut requests: EventReader<TimeTravelRequest>,
+    mut game_state: ResMut<NextState<GameState>>,
+) {
+    for _ in requests.read() {
+        game_state.set(GameState::Explore);
+    }
+}
index 134d4b0..71dfd1d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,16 +2,21 @@ mod buildings;
=mod camera;
=mod date;
=mod day_month;
+mod explore;
=mod ground;
+mod history;
=mod sun;
=
=use bevy::prelude::*;
+use bevy_egui::EguiPlugin;
=use buildings::BuildingsPlugin;
=use date::Date;
=use date::DatePlugin;
=use date::NewMonth;
=use day_month::DayMonth;
+use explore::ExplorePlugin;
=use ground::GroundPlugin;
+use history::HistoryPlugin;
=use sun::SunPlugin;
=
=const BEGINNING: i32 = 1860;
@@ -38,6 +43,9 @@ fn main() {
=        .add_plugins(BuildingsPlugin)
=        .add_plugins(DatePlugin)
=        .add_plugins(SunPlugin)
+        .add_plugins(HistoryPlugin)
+        .add_plugins(ExplorePlugin)
+        .add_plugins(EguiPlugin)
=        .add_systems(Startup, greet)
=        .add_systems(Update, switch_states)
=        .add_systems(OnEnter(GameState::Explore), setup_exploration)

Set simulation to 15 years

On by Tad Lispy

Move camera away so more buildings can be seen.

Make sidebar scrollable, so all the snapshots can be accessed.

index 634bf13..ab581f3 100644
--- a/src/explore.rs
+++ b/src/explore.rs
@@ -23,19 +23,21 @@ fn paint_ui(
=    egui::SidePanel::right("explore_ui").show(contexts.ctx_mut(), |ui| {
=        ui.heading("Explore");
=
-        for (timestamp, snapshot) in history
-            .snapshots
-            .iter()
-            // TODO: Consider using BTreeMap for always sorted snapshots. Or even ditch keys all together and use a vector?
-            .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
-        {
-            let month = timestamp.month.clone();
-            let year = timestamp.year;
-            let button = ui.button(format!("{month} {year}"));
-            if button.clicked() {
-                info!("Time travel to {timestamp}");
-                requests.send(TimeTravelRequest(snapshot.clone()));
+        egui::ScrollArea::vertical().show(ui, |ui| {
+            for (timestamp, snapshot) in history
+                .snapshots
+                .iter()
+                // TODO: Consider using BTreeMap for always sorted snapshots. Or even ditch keys all together and use a vector?
+                .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
+            {
+                let month = timestamp.month.clone();
+                let year = timestamp.year;
+                let button = ui.button(format!("{month} {year}"));
+                if button.clicked() {
+                    info!("Time travel to {timestamp}");
+                    requests.send(TimeTravelRequest(snapshot.clone()));
+                }
=            }
-        }
+        })
=    });
=}
index 71dfd1d..021646b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,7 +20,7 @@ use history::HistoryPlugin;
=use sun::SunPlugin;
=
=const BEGINNING: i32 = 1860;
-const DURATION: i32 = 1;
+const DURATION: i32 = 15;
=
=fn main() {
=    App::new()
@@ -36,7 +36,7 @@ fn main() {
=        .init_state::<GameState>()
=        .add_plugins(camera::CameraPlugin(camera::CameraSettings {
=            elevation: 5.,
-            distance: 12.,
+            distance: 20.,
=            rps: 0.01,
=        }))
=        .add_plugins(GroundPlugin)