diff --git a/Cargo.lock b/Cargo.lock index 6a33ef9..7a80cf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -226,50 +226,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "fluent" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" -dependencies = [ - "fluent-bundle", - "unic-langid", -] - -[[package]] -name = "fluent-bundle" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" -dependencies = [ - "fluent-langneg", - "fluent-syntax", - "intl-memoizer", - "intl_pluralrules", - "rustc-hash 1.1.0", - "self_cell 0.10.3", - "smallvec", - "unic-langid", -] - -[[package]] -name = "fluent-langneg" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0" -dependencies = [ - "unic-langid", -] - -[[package]] -name = "fluent-syntax" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" -dependencies = [ - "thiserror", -] - [[package]] name = "fnv" version = "1.0.7" @@ -326,1048 +282,33 @@ dependencies = [ ] [[package]] -name = "futures-core" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" - -[[package]] -name = "futures-executor" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" - -[[package]] -name = "futures-macro" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" - -[[package]] -name = "futures-task" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" - -[[package]] -name = "futures-util" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi", -] - -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "http" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" - -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "memchr" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mio" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "openssl" -version = "0.10.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quest-contract" -version = "0.1.0" -dependencies = [ - "chrono", - "js-sys", - "once_cell", - "reqwest", - "serde", - "serde_json", - "sha2", - "tempfile", - "thiserror", - "tokio", - "toml", - "wasm-bindgen", - "web-sys", - "wiremock", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] -name = "tempfile" -version = "3.27.0" +name = "futures-executor" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "thiserror" -version = "1.0.69" +name = "futures-io" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "futures-macro" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", @@ -1375,97 +316,115 @@ dependencies = [ ] [[package]] -name = "tinystr" -version = "0.8.3" +name = "futures-sink" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] -name = "tokio" -version = "1.53.1" +name = "futures-task" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", + "slab", ] [[package]] -name = "tokio-macros" -version = "2.7.1" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "proc-macro2", - "quote", - "syn", + "typenum", + "version_check", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "native-tls", - "tokio", + "cfg-if", + "libc", + "wasi", ] [[package]] -name = "tokio-rustls" -version = "0.26.4" +name = "getrandom" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ - "rustls", - "tokio", + "cfg-if", + "libc", + "r-efi", ] [[package]] -name = "tokio-util" -version = "0.7.19" +name = "h2" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ + "atomic-waker", "bytes", + "fnv", "futures-core", "futures-sink", - "libc", - "pin-project-lite", + "http", + "indexmap", + "slab", "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "toml" -version = "0.8.23" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", + "bytes", + "itoa", ] [[package]] -name = "toml_datetime" -version = "0.6.11" +name = "http-body" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ - "serde", - + "bytes", + "http", ] [[package]] @@ -1708,25 +667,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "intl-memoizer" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" -dependencies = [ - "type-map", - "unic-langid", -] - -[[package]] -name = "intl_pluralrules" -version = "7.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" -dependencies = [ - "unic-langid", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -1947,12 +887,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.106" @@ -1967,8 +901,6 @@ name = "quest-contract" version = "0.1.0" dependencies = [ "chrono", - "fluent", - "fluent-bundle", "reqwest", "serde", "serde_json", @@ -1977,7 +909,6 @@ dependencies = [ "thiserror", "tokio", "toml", - "unic-langid", "wiremock", ] @@ -2088,18 +1019,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - [[package]] name = "rustix" version = "1.1.4" @@ -2128,9 +1047,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "zeroize", ] @@ -2196,21 +1115,6 @@ dependencies = [ "libc", ] -[[package]] -name = "self_cell" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" -dependencies = [ - "self_cell 1.3.0", -] - -[[package]] -name = "self_cell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" - [[package]] name = "serde" version = "1.0.228" @@ -2428,7 +1332,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", - "serde_core", "zerovec", ] @@ -2684,6 +1587,76 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.20.1" @@ -3112,8 +2085,6 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "serde", - "yoke", "zerofrom", "zerovec-derive", diff --git a/src/generator/mod.rs b/src/generator/mod.rs new file mode 100644 index 0000000..8fe3af0 --- /dev/null +++ b/src/generator/mod.rs @@ -0,0 +1,512 @@ +//! Generator module. +//! +//! Procedurally generates logic puzzles at runtime based on configurable +//! parameters: difficulty, category, and seed. + +use crate::difficulty::Difficulty; +use crate::puzzle::{Condition, Effect, Puzzle}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; + +// ── Category ───────────────────────────────────────────────────────────────── + +/// Puzzle theme categories for procedural generation. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub enum Category { + /// Digital logic, circuits, switches, and relays. + Logic, + /// Cryptography, ciphers, and codes. + Cryptography, + /// Dungeons, traps, braziers, and ancient mechanisms. + Dungeon, + /// Alchemical recipes, crucibles, and reagents. + Alchemy, + /// Patterns, sequences, and frequency tuning. + Pattern, +} + +impl Category { + /// Returns the string representation of this category. + pub fn as_str(&self) -> &'static str { + match self { + Category::Logic => "Logic", + Category::Cryptography => "Cryptography", + Category::Dungeon => "Dungeon", + Category::Alchemy => "Alchemy", + Category::Pattern => "Pattern", + } + } +} + +impl fmt::Display for Category { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl FromStr for Category { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "logic" => Ok(Category::Logic), + "cryptography" | "crypto" | "cipher" => Ok(Category::Cryptography), + "dungeon" => Ok(Category::Dungeon), + "alchemy" => Ok(Category::Alchemy), + "pattern" => Ok(Category::Pattern), + _ => Err(format!("Unknown puzzle category: '{s}'")), + } + } +} + +impl From<&str> for Category { + fn from(s: &str) -> Self { + s.parse().unwrap_or(Category::Logic) + } +} + +// ── Deterministic PRNG ──────────────────────────────────────────────────────── + +/// Simple pseudo-random number generator (PCG / Xorshift variant) +/// initialized with a seed to guarantee cross-platform determinism. +#[derive(Debug, Clone)] +pub struct Prng { + state: u64, +} + +impl Prng { + /// Creates a new PRNG seeded with `seed`. + pub fn new(seed: u64) -> Self { + let mut s = seed.wrapping_add(0x9e3779b97f4a7c15); + s = (s ^ (s >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + s = (s ^ (s >> 27)).wrapping_mul(0x94d049bb133111eb); + Self { + state: s ^ (s >> 31), + } + } + + /// Generates the next pseudo-random `u64`. + pub fn next_u64(&mut self) -> u64 { + self.state = self + .state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + let mut x = self.state; + x ^= x >> 30; + x = x.wrapping_mul(0xbf58476d1ce4e5b9); + x ^= x >> 27; + x = x.wrapping_mul(0x94d049bb133111eb); + x ^ (x >> 31) + } + + /// Generates an integer in the inclusive range `[min, max]`. + pub fn next_range(&mut self, min: usize, max: usize) -> usize { + if min >= max { + return min; + } + let span = (max - min + 1) as u64; + min + (self.next_u64() % span) as usize + } + + /// Randomly shuffles a slice in place. + pub fn shuffle(&mut self, slice: &mut [T]) { + for i in (1..slice.len()).rev() { + let j = self.next_range(0, i); + slice.swap(i, j); + } + } +} + +// ── Difficulty Parameters ──────────────────────────────────────────────────── + +/// Scaling parameters determined by [`Difficulty`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DifficultyParams { + /// Number of sequential steps required for the primary solution chain. + pub chain_length: usize, + /// Number of red-herring decoy conditions added to increase puzzle complexity. + pub red_herrings: usize, +} + +impl DifficultyParams { + /// Returns the generation parameters for the given difficulty tier. + pub fn for_difficulty(difficulty: Difficulty) -> Self { + match difficulty { + Difficulty::Easy => DifficultyParams { + chain_length: 2, + red_herrings: 1, + }, + Difficulty::Medium => DifficultyParams { + chain_length: 4, + red_herrings: 2, + }, + Difficulty::Hard => DifficultyParams { + chain_length: 6, + red_herrings: 4, + }, + } + } +} + +// ── Category Step Pools ────────────────────────────────────────────────────── + +fn step_pool_for_category(category: Category) -> Vec<(&'static str, &'static str)> { + match category { + Category::Logic => vec![ + ("inspect_circuit", "Inspect the primary logic circuit board"), + ("toggle_switch_alpha", "Toggle the alpha input switch"), + ("flip_relay_beta", "Flip the magnetic relay beta switch"), + ("calibrate_gate_gamma", "Calibrate the gamma logic gate"), + ("bridge_bus_connection", "Bridge the central data bus connection"), + ("ground_wire_delta", "Ground the delta voltage wire"), + ("bypass_safety_fuse", "Bypass the secondary safety fuse"), + ("verify_truth_table", "Verify the output truth table state"), + ("disarm_logic_trap", "Disarm the circuit feedback trap"), + ("sync_clock_pulse", "Synchronize the master clock pulse"), + ], + Category::Cryptography => vec![ + ("intercept_cipher_text", "Intercept the encrypted cipher payload"), + ("decode_substitution", "Decode the monoalphabetic substitution key"), + ("shift_caesar_offset", "Shift the Caesar cipher rotary wheel"), + ("calculate_crc_checksum", "Calculate the 32-bit CRC checksum"), + ("apply_xor_mask", "Apply the stream cipher XOR mask"), + ("decrypt_header_bytes", "Decrypt the payload header block"), + ("verify_hash_signature", "Verify the cryptographic SHA signature"), + ("crack_rsa_modulus", "Decompose the public RSA modulus"), + ("extract_secret_nonce", "Extract the one-time secret initialization vector"), + ("authorize_key_pair", "Authorize the decrypted master key pair"), + ], + Category::Dungeon => vec![ + ("find_iron_key", "Locate the heavy iron key in the wall alcove"), + ("unlock_dungeon_grate", "Unlock the reinforced iron grate"), + ("disarm_pressure_plate", "Disarm the concealed floor pressure plate"), + ("ignite_stone_brazier", "Ignite the ritual stone brazier"), + ("pull_shadow_lever", "Pull the concealed wall lever"), + ("align_gargoyle_statue", "Align the twin gargoyle statues"), + ("retrieve_ancient_relic", "Retrieve the relic from the stone pedestal"), + ("quench_flame_trap", "Quench the fire-breathing wall trap"), + ("rotate_sun_disk", "Rotate the celestial sun disk dial"), + ("unseal_vault_door", "Unseal the heavy vault door mechanism"), + ], + Category::Alchemy => vec![ + ("gather_fire_herb", "Gather dried dragonfire herb leaves"), + ("crush_moonstone_dust", "Crush moonstone into fine powder"), + ("heat_brass_crucible", "Heat the brass crucible to boiling point"), + ("distill_silver_essence", "Distill the pure silver liquid essence"), + ("mix_catalyst_agent", "Mix the stabilizing catalyst agent"), + ("quench_reaction_vial", "Quench the chemical reaction vial in water"), + ("filter_sediment_precipitate", "Filter out the solid crystalline sediment"), + ("seal_alchemical_flask", "Seal the glowing alchemical flask"), + ("purify_elixir_compound", "Purify the master potion compound"), + ("transmute_base_metal", "Transmute the base alloy into transmuted gold"), + ], + Category::Pattern => vec![ + ("observe_light_sequence", "Observe the repeating light sequence"), + ("align_rhythmic_dial", "Align the rhythmic resonance dial"), + ("match_color_nodes", "Match the glowing color node pairs"), + ("rotate_concentric_rings", "Rotate the inner concentric rings"), + ("balance_crystal_harmonics", "Balance the crystal acoustic harmonics"), + ("calibrate_frequency_wave", "Calibrate the sine frequency wave"), + ("solve_matrix_grid", "Solve the 3x3 symbol matrix grid"), + ("synchronize_pulses", "Synchronize the harmonic energy pulses"), + ("lock_pattern_phase", "Lock the phase lock loop in position"), + ("stabilize_resonance_core", "Stabilize the main resonance core"), + ], + } +} + +fn red_herring_pool_for_category(category: Category) -> Vec<(&'static str, &'static str)> { + match category { + Category::Logic => vec![ + ("cut_red_wire", "Cut the decoy red wire (herring)"), + ("overload_capacitor", "Overload the secondary capacitor (herring)"), + ("press_emergency_dump", "Press the emergency memory dump button (herring)"), + ("short_circuit_bus", "Short circuit the auxiliary bus bar (herring)"), + ("disable_cooling_fan", "Disable the chassis cooling fan (herring)"), + ], + Category::Cryptography => vec![ + ("parse_decoy_flag", "Parse the decoy secret flag (herring)"), + ("inject_null_payload", "Inject null padding bytes into stream (herring)"), + ("corrupt_index_table", "Corrupt the dictionary lookup table (herring)"), + ("reverse_endianness", "Reverse payload byte endianness (herring)"), + ("spoof_mac_address", "Spoof the network hardware address (herring)"), + ], + Category::Dungeon => vec![ + ("open_mimic_chest", "Open the suspicious wooden chest (herring)"), + ("drink_murky_potion", "Drink the unlabelled green potion (herring)"), + ("step_on_loose_tile", "Step on the loose crumbling floor tile (herring)"), + ("touch_cursed_idol", "Touch the glowing jade idol (herring)"), + ("ring_warning_bell", "Ring the rusted warning bell (herring)"), + ], + Category::Alchemy => vec![ + ("add_excess_sulfur", "Add excess sulfur powder to crucible (herring)"), + ("boil_potion_dry", "Boil the mixture until completely dry (herring)"), + ("spill_acid_solvent", "Spill acidic solvent onto laboratory bench (herring)"), + ("inhale_noxious_vapor", "Inhale the strange purple vapor (herring)"), + ("freeze_mixture_solid", "Freeze the liquid mixture prematurely (herring)"), + ], + Category::Pattern => vec![ + ("disrupt_wave_amplitude", "Disrupt the sound wave amplitude (herring)"), + ("invert_color_spectrum", "Invert the visual color spectrum (herring)"), + ("randomize_dial_positions", "Randomly spin all dials (herring)"), + ("shatter_tuning_fork", "Shatter the acoustic tuning fork (herring)"), + ("scramble_matrix_symbols", "Scramble the matrix tile order (herring)"), + ], + } +} + +// ── Generator ──────────────────────────────────────────────────────────────── + +/// Procedurally generates a logic puzzle based on `difficulty`, `category`, and `seed`. +/// +/// Generation is completely deterministic: identical `(difficulty, category, seed)` +/// calls will always yield identical [`Puzzle`] structs with identical condition +/// ordering and content hashes. +pub fn generate(difficulty: Difficulty, category: impl Into, seed: u64) -> Puzzle { + let category = category.into(); + let params = DifficultyParams::for_difficulty(difficulty); + let mut rng = Prng::new(seed); + + let step_pool = step_pool_for_category(category); + let herring_pool = red_herring_pool_for_category(category); + + // Select step_pool items deterministically for main chain + let mut selected_chain: Vec<(&'static str, &'static str)> = Vec::new(); + let mut indices: Vec = (0..step_pool.len()).collect(); + rng.shuffle(&mut indices); + + for &idx in indices.iter().take(params.chain_length) { + selected_chain.push(step_pool[idx]); + } + + // Select red herrings + let mut selected_herrings: Vec<(&'static str, &'static str)> = Vec::new(); + let mut herring_indices: Vec = (0..herring_pool.len()).collect(); + rng.shuffle(&mut herring_indices); + + for &idx in herring_indices.iter().take(params.red_herrings) { + selected_herrings.push(herring_pool[idx]); + } + + // Combine conditions + let mut conditions = Vec::new(); + for (id, desc) in &selected_chain { + conditions.push(Condition::new(*id, *desc)); + } + for (id, desc) in &selected_herrings { + conditions.push(Condition::new(*id, *desc)); + } + + // Shuffle conditions so red herrings are interspersed deterministically + rng.shuffle(&mut conditions); + + // Create puzzle ID & description + let puzzle_id = format!( + "{}-{:?}-{:08x}", + category.as_str().to_lowercase(), + difficulty, + (seed & 0xffff_ffff) as u32 + ); + let description = format!( + "[{}] Complete the procedural {} sequence to solve the puzzle.", + category.as_str(), + format!("{:?}", difficulty).to_lowercase() + ); + + // Effects scaling with difficulty + let score = difficulty.apply_multiplier(100); + let reward = difficulty.reward_amount(); + let effects = vec![ + Effect::AwardScore(score), + Effect::GrantItem { + item_id: format!("{}_badge", category.as_str().to_lowercase()), + quantity: reward / 10, + }, + Effect::UnlockAchievement(format!("Master of {}", category.as_str())), + ]; + + let mut puzzle = Puzzle::new(puzzle_id, description, conditions, effects); + puzzle.generate_content_hash(); + debug_assert!( + validate_solvability(&puzzle), + "Generated puzzle failed solvability check" + ); + puzzle +} + +// ── Daily Challenge ────────────────────────────────────────────────────────── + +/// Generates a daily challenge puzzle seeded by the given `date` (or current UTC date if `None`). +pub fn generate_daily_challenge(date: Option) -> Puzzle { + use chrono::Datelike; + let target_date = date.unwrap_or_else(|| chrono::Utc::now().date_naive()); + let seed = (target_date.year() as u64) * 10000 + + (target_date.month() as u64) * 100 + + (target_date.day() as u64); + generate(Difficulty::Medium, Category::Logic, seed) +} + +// ── Solvability Validation ─────────────────────────────────────────────────── + +/// Validates that `puzzle` is solvable by passing its conditions through +/// the logic evaluator [`crate::logic::evaluate`]. +/// +/// Returns `true` if at least one valid solution pathway exists. +pub fn validate_solvability(puzzle: &Puzzle) -> bool { + use crate::logic::{self, Condition as LogicCondition}; + use std::collections::HashMap; + + if puzzle.conditions.is_empty() { + return true; + } + + // Build a logic condition tree requiring all puzzle conditions to be satisfied + let logic_tree = LogicCondition::And( + puzzle + .conditions + .iter() + .map(|c| LogicCondition::Fact(c.id.clone())) + .collect(), + ); + + // Context where all required condition facts are set to true + let mut context_map = HashMap::new(); + for c in &puzzle.conditions { + context_map.insert(c.id.as_str(), true); + } + + // Evaluate logic tree against valid context + let logic_solvable = logic::evaluate(&logic_tree, &context_map); + + // Verify engine state transition on a cloned puzzle instance + let mut test_puzzle = puzzle.clone(); + for c in &puzzle.conditions { + test_puzzle.evaluate(&c.id); + } + let state_solvable = test_puzzle.is_solved(); + + logic_solvable && state_solvable +} + +// ── Export Functions ───────────────────────────────────────────────────────── + +/// Exports a [`Puzzle`] to the standard pretty-printed JSON format with a valid content hash. +pub fn export_to_json(puzzle: &Puzzle) -> Result { + let mut p = puzzle.clone(); + if p.content_hash.is_none() { + p.generate_content_hash(); + } + serde_json::to_string_pretty(&p) +} + +/// Exports a [`Puzzle`] to the standard puzzle JSON format on disk at `path` for admin review. +pub fn export_to_file( + puzzle: &Puzzle, + path: impl AsRef, +) -> Result<(), crate::errors::AppError> { + let json = export_to_json(puzzle)?; + std::fs::write(path, json)?; + Ok(()) +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generator_is_deterministic() { + let p1 = generate(Difficulty::Medium, Category::Logic, 42); + let p2 = generate(Difficulty::Medium, Category::Logic, 42); + assert_eq!(p1, p2); + assert_eq!(p1.content_hash, p2.content_hash); + } + + #[test] + fn different_seeds_produce_different_puzzles() { + let p1 = generate(Difficulty::Medium, Category::Logic, 42); + let p2 = generate(Difficulty::Medium, Category::Logic, 43); + assert_ne!(p1.id, p2.id); + assert_ne!(p1.conditions, p2.conditions); + } + + #[test] + fn difficulty_scaling_affects_condition_count() { + let easy = generate(Difficulty::Easy, Category::Dungeon, 100); + let medium = generate(Difficulty::Medium, Category::Dungeon, 100); + let hard = generate(Difficulty::Hard, Category::Dungeon, 100); + + let params_easy = DifficultyParams::for_difficulty(Difficulty::Easy); + let params_medium = DifficultyParams::for_difficulty(Difficulty::Medium); + let params_hard = DifficultyParams::for_difficulty(Difficulty::Hard); + + assert_eq!( + easy.conditions.len(), + params_easy.chain_length + params_easy.red_herrings + ); + assert_eq!( + medium.conditions.len(), + params_medium.chain_length + params_medium.red_herrings + ); + assert_eq!( + hard.conditions.len(), + params_hard.chain_length + params_hard.red_herrings + ); + + assert!(easy.conditions.len() < medium.conditions.len()); + assert!(medium.conditions.len() < hard.conditions.len()); + } + + #[test] + fn category_parsing_and_display() { + assert_eq!("logic".parse::().unwrap(), Category::Logic); + assert_eq!("dungeon".parse::().unwrap(), Category::Dungeon); + assert_eq!("crypto".parse::().unwrap(), Category::Cryptography); + assert_eq!(Category::Alchemy.to_string(), "Alchemy"); + } + + #[test] + fn solvability_validation_confirms_generated_puzzle_is_solvable() { + let puzzle = generate(Difficulty::Hard, Category::Cryptography, 999); + assert!(validate_solvability(&puzzle)); + } + + #[test] + fn export_to_json_produces_valid_stamped_puzzle_json() { + let puzzle = generate(Difficulty::Medium, Category::Pattern, 777); + let json = export_to_json(&puzzle).expect("export to json should succeed"); + + // Should deserialize back to puzzle + let loaded: Puzzle = serde_json::from_str(&json).expect("deserialize exported JSON"); + assert_eq!(loaded.id, puzzle.id); + assert_eq!(loaded.content_hash, puzzle.content_hash); + + // Content hash verification should pass + assert!(loaded.verify_content_hash().is_ok()); + } + + #[test] + fn export_to_file_creates_readable_puzzle_file() { + let dir = tempfile::TempDir::new().unwrap(); + let file_path = dir.path().join("exported_puzzle.json"); + + let puzzle = generate(Difficulty::Easy, Category::Alchemy, 12345); + export_to_file(&puzzle, &file_path).expect("export to file should succeed"); + + // Verify file exists and can be loaded via loader module + let loaded = crate::loader::load_puzzle_file(&file_path).expect("load via loader module"); + assert_eq!(loaded.id, puzzle.id); + assert_eq!(loaded.content_hash, puzzle.content_hash); + } + + #[test] + fn daily_challenge_is_deterministic_for_same_date() { + let date = chrono::NaiveDate::from_ymd_opt(2026, 7, 23).unwrap(); + let puzzle1 = generate_daily_challenge(Some(date)); + let puzzle2 = generate_daily_challenge(Some(date)); + assert_eq!(puzzle1, puzzle2); + assert!(validate_solvability(&puzzle1)); + } +} diff --git a/src/leaderboard/mod.rs b/src/leaderboard/mod.rs index 0006969..077691a 100644 --- a/src/leaderboard/mod.rs +++ b/src/leaderboard/mod.rs @@ -39,21 +39,17 @@ impl Leaderboard { } pub fn insert(&mut self, entry: Entry) { - // If board not full, just insert and sort in descending order if self.entries.len() < self.max_entries { self.entries.push(entry); - self.entries.sort_by(|a, b| b.cmp(a)); // Sort descending (highest first) + self.entries.sort_by(|a, b| b.cmp(a)); return; } - // Board full: compare with lowest-ranked (last after descending sort). - // Our Ord puts higher scores first, so `entry < *worst` means entry - // outranks worst (has a higher score). - if let Some(worst) = self.entries.last() - && entry < *worst - { - self.entries.pop(); - self.entries.push(entry); - self.entries.sort(); + if let Some(worst) = self.entries.last() { + if entry.cmp(worst) == Ordering::Greater { + self.entries.pop(); + self.entries.push(entry); + self.entries.sort_by(|a, b| b.cmp(a)); + } } } diff --git a/src/lib.rs b/src/lib.rs index 61a6348..03894e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,8 +23,4 @@ pub mod nft; pub mod plugin; pub mod score; pub mod timer; -pub mod time; -pub mod persistence; - -#[cfg(feature = "wasm")] -pub mod wasm; +pub mod generator; diff --git a/src/main.rs b/src/main.rs index 097fb0b..eddd685 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,17 +33,8 @@ fn main() { )); } - let mut engine = engine::Engine::new(Duration::from_millis(16)); - - // Register the built-in core logic plugin with the engine - use smart_contract_game::plugin::core_logic_plugin::CoreLogicPlugin; - if let Err(e) = engine.register_plugin(Box::new(CoreLogicPlugin)) { - eprintln!("Failed to register built-in plugins: {e}"); - std::process::exit(1); - } - - if args.iter().any(|a| a == "--list-plugins") { - std::process::exit(run_list_plugins(&engine)); + if args.iter().any(|a| a == "--daily-challenge") { + std::process::exit(run_daily_challenge(&args)); } // Initialize and run the core engine for a short duration to ensure clean startup/shutdown. @@ -145,15 +136,30 @@ fn run_generate_hashes(dir: &str, admin_confirmed: bool, l10n: &L10n) -> i32 { } } -/// Runs the `--list-plugins` command: enumerates all registered plugins -/// and prints their metadata. Returns the process exit code. -fn run_list_plugins(engine: &smart_contract_game::engine::Engine) -> i32 { - let plugins = engine.list_plugins(); - println!("Registered Plugins ({} total):", plugins.len()); - for (id, meta) in plugins { - println!("\n• ID: {id}"); - println!(" Name: {} (v{})", meta.name, meta.version); - println!(" Summary: {}", meta.description); +/// Runs the `--daily-challenge` command: generates and launches a seeded puzzle +/// based on the current date (or a specific date passed with `--date YYYY-MM-DD`). +fn run_daily_challenge(args: &[String]) -> i32 { + use smart_contract_game::generator::generate_daily_challenge; + + let date_arg = args + .iter() + .position(|a| a == "--date") + .and_then(|i| args.get(i + 1)) + .and_then(|d| chrono::NaiveDate::parse_from_str(d, "%Y-%m-%d").ok()); + + let puzzle = generate_daily_challenge(date_arg); + + println!("=================================================="); + println!(" DAILY CHALLENGE PUZZLE "); + println!("=================================================="); + println!("ID: {}", puzzle.id); + println!("Description: {}", puzzle.description); + println!("Conditions: {}", puzzle.conditions.len()); + for (i, cond) in puzzle.conditions.iter().enumerate() { + println!(" {}. [{}] {}", i + 1, cond.id, cond.description); } + println!("Hash: {}", puzzle.content_hash.as_deref().unwrap_or("None")); + println!("=================================================="); + 0 }