diff --git a/.gitignore b/.gitignore index 10ccf42..01438c0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ __pycache__/ *.swp *.swo *~ + +# pytest-cov artefact from `make spelling-helper-test` +.coverage diff --git a/Cargo.lock b/Cargo.lock index f0b9c23..b9047d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "uuid", +] + [[package]] name = "ahash" version = "0.8.12" @@ -9,7 +18,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -24,6 +33,67 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.20", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "ascii" version = "1.1.0" @@ -31,290 +101,1704 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] -name = "autocfg" -version = "1.5.1" +name = "assert_type_match" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "bitflags" -version = "2.11.1" +name = "async-channel" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] [[package]] -name = "cfg-if" -version = "1.0.4" +name = "async-executor" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] [[package]] -name = "choice" -version = "0.0.2" +name = "async-task" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b71fc821deaf602a933ada5c845d088156d0cdf2ebf43ede390afe93466553" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] [[package]] -name = "chunked_transfer" -version = "1.5.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] [[package]] -name = "console" -version = "0.16.3" +name = "autocfg" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ - "encode_unicode", - "libc", - "windows-sys", + "serde", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "bevy" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "4bfadbebfc6599aa59289b754ac30023959854304f3d393da2cf62bb3cd5df8f" +dependencies = [ + "bevy_internal", +] [[package]] -name = "dashmap" -version = "6.1.0" +name = "bevy_a11y" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "3c02cd43f914ef7a0f5b16f8a3d88c378c07c92f1a8c32ac5ac4d4f73e55f9e2" dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "accesskit", + "bevy_app", + "bevy_derive", + "bevy_ecs", + "bevy_reflect", ] [[package]] -name = "diff" -version = "0.1.13" +name = "bevy_android" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "ae5ea5245a845cefa8efb5ff7c2a8a36cdebfb99596068780f340f0b5aba6689" +dependencies = [ + "android-activity", +] [[package]] -name = "encode_unicode" -version = "1.0.0" +name = "bevy_app" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +checksum = "5fd2aa472d43b4d081ef41d26300d17bd22cba6d68df2a223eb358782fa40bc6" +dependencies = [ + "bevy_derive", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "ctrlc", + "downcast-rs", + "log", + "thiserror 2.0.20", + "variadics_please", +] [[package]] -name = "equivalent" -version = "1.0.2" +name = "bevy_color" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "5e5370beae13bea2db64744f370bb33566aa770dff91c2beae245ce82217fbbc" +dependencies = [ + "bevy_math", + "bevy_reflect", + "bytemuck", + "derive_more 2.1.1", + "serde", + "thiserror 2.0.20", + "wgpu-types", +] [[package]] -name = "errno" -version = "0.3.14" +name = "bevy_derive" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "9d2a03321f3bd8016a041b3ae6afe83e636b3540fbc61d571bf069a21fda3231" dependencies = [ - "libc", - "windows-sys", + "bevy_macro_utils", + "quote", + "syn 2.0.119", ] [[package]] -name = "eyre" -version = "0.6.14" +name = "bevy_diagnostic" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" +checksum = "07a3a736f15486650c4c6866798a78d625c7063c991a3cc7b13f1577d89c82ae" dependencies = [ - "autocfg", - "indenter", - "once_cell", + "atomic-waker", + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_tasks", + "bevy_time", + "const-fnv1a-hash", + "log", + "serde", ] [[package]] -name = "fastrand" -version = "2.4.1" +name = "bevy_ecs" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "a350b2e4c2e47f7903d688affb8a723b31bca63af9789f1da88703f1ba04e354" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags", + "bumpalo", + "concurrent-queue", + "derive_more 2.1.1", + "fixedbitset", + "indexmap", + "log", + "nonmax", + "serde", + "slotmap", + "smallvec", + "thiserror 2.0.20", + "variadics_please", +] [[package]] -name = "futures-core" -version = "0.3.32" +name = "bevy_ecs_macro_logic" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "76a10fef45a3e7dad717ba03e8b5f7ecdf3b3fd1cd1c5e425793525938e90d5f" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "futures-macro" -version = "0.3.32" +name = "bevy_ecs_macros" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "3916d712264b6f3ec9c964ba10719450ea371e537e202a2ffcfa9f6a5e68169d" dependencies = [ + "bevy_ecs_macro_logic", + "bevy_macro_utils", "proc-macro2", "quote", "syn 2.0.119", ] [[package]] -name = "futures-task" -version = "0.3.32" +name = "bevy_input" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b22e8b1119ff5c0b551cc11b672d25732c4e3eda72aceabc380595bd0e7bbe88" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "derive_more 2.1.1", + "log", + "thiserror 2.0.20", +] [[package]] -name = "futures-timer" -version = "3.0.3" +name = "bevy_input_focus" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "c4d5573eb503387f73d12acde3010d49c808c790d34691e2fb410fc54b8c72b9" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_reflect", + "bevy_window", + "log", + "thiserror 2.0.20", +] [[package]] -name = "futures-util" -version = "0.3.32" +name = "bevy_internal" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "4ba99ca2e501f14f4af3322d66ae41b5c5b6ebd3aac4d00cda47dea4c00290ae" dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite", - "slab", + "bevy_a11y", + "bevy_android", + "bevy_app", + "bevy_color", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_input", + "bevy_input_focus", + "bevy_math", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_state", + "bevy_tasks", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bevy_window", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "bevy_macro_utils" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "7164fe229422295bba15f1885048f34f4660db069aacb895208f996b0c7784fb" dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", + "proc-macro2", + "quote", + "syn 2.0.119", + "toml_edit", ] [[package]] -name = "glob" -version = "0.3.3" +name = "bevy_math" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "98ead494dcfaa9b4594eed7f09a27d868b7cc7fc9ebd655c5643322778f4a8a2" +dependencies = [ + "approx", + "arrayvec", + "bevy_reflect", + "derive_more 2.1.1", + "glam", + "itertools", + "libm", + "rand 0.10.2", + "rand_distr", + "serde", + "thiserror 2.0.20", + "variadics_please", +] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "bevy_platform" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "fd610a417e7b4a3b9602cf11264d16cbbff8eabe689c5fed8be4e84c6a2680a3" +dependencies = [ + "critical-section", + "foldhash", + "futures-channel", + "futures-lite", + "hashbrown 0.16.1", + "js-sys", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", + "wasm-bindgen", + "wasm-bindgen-futures", + "windows-sys 0.61.2", +] [[package]] -name = "hashbrown" -version = "0.17.1" +name = "bevy_ptr" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +checksum = "1974de4b140f102fded747229ff4a61921acec07f02f0d1dcdadfe9ce177d0ff" [[package]] -name = "httpdate" -version = "1.0.3" +name = "bevy_reflect" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "9b76c3d056da3e299b84652ccf914c487b786857225d8ffbc8fc9509441a9e6b" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more 2.1.1", + "disqualified", + "downcast-rs", + "erased-serde", + "foldhash", + "glam", + "indexmap", + "serde", + "smallvec", + "smol_str", + "thiserror 2.0.20", + "uuid", + "variadics_please", + "wgpu-types", +] [[package]] -name = "id-set" -version = "0.2.2" +name = "bevy_reflect_derive" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9633fadf6346456cf8531119ba4838bc6d82ac4ce84d9852126dd2aa34d49264" +checksum = "5fc5f820ee52afeca6d3bb83f15c1bf61ea6196c840a761a21a8bc266a3b166e" +dependencies = [ + "bevy_macro_utils", + "indexmap", + "proc-macro2", + "quote", + "syn 2.0.119", + "uuid", +] [[package]] -name = "indenter" -version = "0.3.4" +name = "bevy_state" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" +checksum = "f69ce6a0066eb4626165e2f8c85d3cf4808cfe4cb2027df8840d5cbfe8a3428d" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_state_macros", + "bevy_utils", + "log", + "variadics_please", +] [[package]] -name = "indexmap" -version = "2.14.0" +name = "bevy_state_macros" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "a36dd49049023a0abbd0bd1e2b99580ed83a18c5351c64caba4c06e3b584cf9c" dependencies = [ - "equivalent", - "hashbrown 0.17.1", + "bevy_macro_utils", + "quote", + "syn 2.0.119", ] [[package]] -name = "insta" -version = "1.48.0" +name = "bevy_tasks" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" +checksum = "1f787d4ba7237b64b30bd2d690a8cbae1ba775e0eec326711c446654cb720e7c" dependencies = [ - "console", - "once_cell", - "serde", - "similar", - "tempfile", + "async-executor", + "async-task", + "atomic-waker", + "bevy_platform", + "crossbeam-queue", + "derive_more 2.1.1", + "futures-lite", + "heapless", + "web-task", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "bevy_time" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "f6896d83d7ed32a2f89da127b7aeddf01eb702e603897cf5ddac231480a1870e" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "crossbeam-channel", + "log", + "serde", +] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "bevy_transform" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "d94cc6065320ebeb58ae88a0863a4c4c2a642c6db698e283eb50be01a834ca5a" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "derive_more 2.1.1", + "serde", + "thiserror 2.0.20", +] [[package]] -name = "libc" -version = "0.2.186" +name = "bevy_utils" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "de51fa7ea2e95d4c2d1278c5629285d59eec951cc60c992964c03f30910ef5a1" +dependencies = [ + "async-channel", + "bevy_platform", + "disqualified", + "indexmap", + "thread_local", +] [[package]] -name = "linux-raw-sys" -version = "0.12.1" +name = "bevy_window" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "38373ee93a9248c14afe41d2025f4aa5e68b004b06ae7702816b180e72b39ea3" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "log", + "raw-window-handle", + "serde", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cap-primitives" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0bf07d379916947be6c4a07f43684153d710a2896c31f9e97781362895596c" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix", + "rustix-linux-procfs", + "windows-sys 0.59.0", + "winx", +] + +[[package]] +name = "cap-std" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a59e59fa26472d29680ece6a9f8ee8b0551a719a33df2f5240bde065ecbddfd7" +dependencies = [ + "camino", + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "choice" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b71fc821deaf602a933ada5c845d088156d0cdf2ebf43ede390afe93466553" + +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "const-fnv1a-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ctrlc" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" +dependencies = [ + "dispatch2", + "nix", + "windows-sys 0.61.2", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "eyre" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3" +dependencies = [ + "autocfg", + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml 0.5.11", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fluent" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "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.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" +dependencies = [ + "memchr", + "thiserror 2.0.20", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[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 6.0.0", +] + +[[package]] +name = "gherkin" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b79820c0df536d1f3a089a2fa958f61cb96ce9e0f3f8f507f5a31179567755" +dependencies = [ + "heck", + "peg", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "textwrap", + "thiserror 1.0.69", + "typed-builder", +] + +[[package]] +name = "glam" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0" +dependencies = [ + "bytemuck", + "libm", + "serde_core", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "googletest" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b5e2f2b556b7b90297a5a35c8267dd43a537923d2b329beefdba2b4ec19d94" +dependencies = [ + "googletest_macro", + "num-traits", + "regex", + "rustversion", +] + +[[package]] +name = "googletest_macro" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae6abc96141edd26bf5aeec0f119c129c44de3ced09e5073711a02cb74725d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", + "serde", + "serde_core", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "i18n-config" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef" +dependencies = [ + "basic-toml", + "log", + "serde", + "serde_derive", + "thiserror 1.0.69", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a217bbb075dcaefb292efa78897fc0678245ca67f265d12c351e42268fcb0305" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "log", + "parking_lot", + "rust-embed", + "sys-locale", + "thiserror 1.0.69", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "id-set" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9633fadf6346456cf8531119ba4838bc6d82ac4ce84d9852126dd2aa34d49264" + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "insta" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" +dependencies = [ + "console", + "once_cell", + "serde", + "similar", + "tempfile", +] + +[[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 = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +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 = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "newt-hype" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8b7b69b0eafaa88ec8dc9fe7c3860af0a147517e5207cfbd0ecd21cd7cde18" + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "scopeguard", + "windows-sys 0.61.2", ] [[package]] -name = "log" -version = "0.4.29" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] [[package]] -name = "memchr" -version = "2.8.0" +name = "num_enum" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] [[package]] -name = "nohash-hasher" -version = "0.2.0" +name = "num_enum_derive" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "nu-ansi-term" -version = "0.50.3" +name = "objc2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ - "windows-sys", + "objc2-encode", ] +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -338,12 +1822,54 @@ dependencies = [ "windows-link", ] +[[package]] +name = "peg" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -372,6 +1898,36 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[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.107" @@ -381,6 +1937,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.4", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.47" @@ -396,6 +1977,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.9.4" @@ -403,7 +1990,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -413,7 +2010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", ] [[package]] @@ -422,9 +2019,40 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_distr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" +dependencies = [ + "num-traits", + "rand 0.10.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", ] +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -480,6 +2108,99 @@ dependencies = [ "rstest_macros", ] +[[package]] +name = "rstest-bdd" +version = "0.6.0-beta3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53b8195de84b6694b210c967726e2a6e4bd82b3394951ad99b85062c7eca6b2" +dependencies = [ + "ctor", + "derive_more 0.99.20", + "fluent", + "gherkin", + "hashbrown 0.16.1", + "i18n-embed", + "inventory", + "log", + "regex", + "rstest-bdd-patterns", + "rstest-bdd-policy", + "rust-embed", + "serde", + "serde_json", + "thiserror 2.0.20", + "unic-langid", +] + +[[package]] +name = "rstest-bdd-harness" +version = "0.6.0-beta3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2994169999c2037287129466b9c576cf51add440bb7d30ee16f712d4e2edf5" +dependencies = [ + "cargo_metadata", + "thiserror 2.0.20", + "tracing", +] + +[[package]] +name = "rstest-bdd-harness-bevy" +version = "0.1.0" +dependencies = [ + "bevy", + "googletest", + "pretty_assertions", + "proptest", + "rstest", + "rstest-bdd", + "rstest-bdd-harness", + "rstest-bdd-macros", + "skyjoust-test-macros", + "tracing", +] + +[[package]] +name = "rstest-bdd-macros" +version = "0.6.0-beta3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080cc7749d3c8b2040c0f8be7faf350817342daade1d56df744655e33e9640" +dependencies = [ + "camino", + "cap-std", + "cfg-if", + "convert_case 0.6.0", + "gherkin", + "newt-hype", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "rstest-bdd-harness", + "rstest-bdd-patterns", + "rstest-bdd-policy", + "syn 2.0.119", + "thiserror 2.0.20", + "walkdir", +] + +[[package]] +name = "rstest-bdd-patterns" +version = "0.6.0-beta3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9773a23087aa9d460f11a982232758e9438f9b09bf297eb1dac748b745a324bd" +dependencies = [ + "gherkin", + "regex", + "thiserror 2.0.20", +] + +[[package]] +name = "rstest-bdd-policy" +version = "0.6.0-beta3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de569456285584981fdd241e1a87152e39fd2c3029ea1b88948b086bfbfbd6" + [[package]] name = "rstest_macros" version = "0.26.1" @@ -498,6 +2219,47 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rust-embed" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" +dependencies = [ + "mime_guess", + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -517,7 +2279,44 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", ] [[package]] @@ -526,11 +2325,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -584,6 +2393,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -593,6 +2413,28 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -638,11 +2480,50 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stateright" @@ -658,12 +2539,22 @@ dependencies = [ "log", "nohash-hasher", "parking_lot", - "rand", + "rand 0.9.4", "serde", "serde_json", "tiny_http", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.119" @@ -686,6 +2577,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + [[package]] name = "target-triple" version = "1.0.1" @@ -699,10 +2599,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -714,6 +2614,57 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "thread_local" version = "1.1.10" @@ -735,6 +2686,26 @@ dependencies = [ "log", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "1.1.4+spec-1.1.0" @@ -855,27 +2826,190 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml", + "toml 1.1.4+spec-1.1.0", +] + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "typed-builder" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe83c85a85875e8c4cb9ce4a890f05b23d38cd0d47647db7895d3d2a79566d2" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unic-langid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" +dependencies = [ + "unic-langid-impl", + "unic-langid-macros", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" +dependencies = [ + "serde", + "tinystr", +] + +[[package]] +name = "unic-langid-macros" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5957eb82e346d7add14182a3315a7e298f04e1ba4baac36f7f0dbfedba5fc25" +dependencies = [ + "proc-macro-hack", + "tinystr", + "unic-langid-impl", + "unic-langid-macros-impl", ] +[[package]] +name = "unic-langid-macros-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1249a628de3ad34b821ecb1001355bca3940bcb2f88558f1a8bd82e977f75b5" +dependencies = [ + "proc-macro-hack", + "quote", + "syn 2.0.119", + "unic-langid-impl", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" @@ -885,13 +3019,105 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-task" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdc136a53ccd64a1211f107ccc34404769fbcc0f165f1afa065f5d88ab93538" +dependencies = [ + "async-task", + "cfg-if", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu-types" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" +dependencies = [ + "bitflags", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "serde", + "web-sys", +] + [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -900,6 +3126,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -909,6 +3144,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "1.0.4" @@ -918,6 +3217,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen" version = "0.57.1" @@ -950,6 +3259,22 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "serde", + "zerofrom", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index aa7b146..e7717ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [workspace] members = [ ".", + "crates/rstest-bdd-harness-bevy", "crates/skyjoust_stateright_validator", "crates/skyjoust_test_macros", ] diff --git a/crates/rstest-bdd-harness-bevy/Cargo.toml b/crates/rstest-bdd-harness-bevy/Cargo.toml new file mode 100644 index 0000000..86588d5 --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "rstest-bdd-harness-bevy" +version = "0.1.0" +edition = "2024" +description = "Headless Bevy harness adapter for rstest-bdd behavioural tests." +license = "ISC" +publish = false +readme = "README.md" + +[lints] +workspace = true + +[dependencies] +bevy = { version = "0.19.1", default-features = false, features = ["std"] } +# Exact pins: a caret requirement on a pre-release is not a pin. A caret on +# 0.6.0-beta3 admits 0.6.0-beta4, which changes StepContext borrowing. +rstest-bdd-harness = "=0.6.0-beta3" +tracing = "0.1" + +[dev-dependencies] +googletest = "0.14.3" +pretty_assertions = "1" +proptest = "1" +rstest = "0.26" +rstest-bdd = "=0.6.0-beta3" +rstest-bdd-macros = "=0.6.0-beta3" +skyjoust-test-macros = { path = "../skyjoust_test_macros" } \ No newline at end of file diff --git a/crates/rstest-bdd-harness-bevy/README.md b/crates/rstest-bdd-harness-bevy/README.md new file mode 100644 index 0000000..923bdc5 --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/README.md @@ -0,0 +1,65 @@ +# `rstest-bdd-harness-bevy` + +A reusable Bevy harness adapter for `rstest-bdd` behavioural tests. This crate +incubates in the Skyjoust workspace but depends on no Skyjoust or Lille code, +so it can move to its own repository as a directory move plus a dependency +rewire. Game-specific setup belongs in the downstream profile types defined by +each consumer. + +This milestone ships the scaffold and its profile functions: a headless Bevy +`0.19.1` application driven from a Gherkin scenario through the ordinary +`cargo test` harness. The harness types (`BevyScenario`, `BevyProfile`, +`BevyHarness`, and their companions) belong to roadmap task `0.5.1.2`. See +[the harness design](../../docs/rstest-bdd-harness-bevy-design.md) and +[ADR 007](../../docs/adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md). + +## Run the tests + +```bash +cargo test -p rstest-bdd-harness-bevy +``` + +Thirteen tests pass: five unit tests for the profile functions, a Gherkin +scenario that advances one headless update tick, a 32-case property test over +tick counts, four extraction-contract tripwire tests, and two doctests. The +behavioural scenario appears as `minimal_app_advances_one_tick`. + +## Files + +- `src/profile.rs` — `add_minimal_plugins` and `minimal_app`, the two public + functions of this milestone. +- `tests/headless_scenario.rs` — the `rstest-bdd` binding with the + `include_str!` feature-file rebuild guard. +- `tests/features/headless_scenario.feature` — the Gherkin specification. +- `tests/tick_properties.rs` — the frame-count tick invariant over `0..=32`. +- `tests/extraction_boundary.rs` — the manifest tripwire that rejects game + crates. + +The unit tests in `src/profile_tests.rs` sit beside their module inside `src/`, +following the repository's sibling-test idiom; they ship with the crate's +source archive when the crate is published after extraction. + +## Extending + +Task `0.5.1.2` adds `BevyScenario`, `BevyProfile`, `BevyHarness`, +`BareBevyProfile`, `MinimalBevyProfile`, and `BevyAttributePolicy`. The profile +type is the single extension seam: every game-specific plugin, resource, and +cleanup hook lives in a downstream implementation of it, never in this crate. + +## Bevy compatibility + +Bevy types appear in this crate's public signatures — `minimal_app` returns +`bevy::app::App` — so a Bevy minor bump is a breaking change here. The +workspace resolves one Bevy line across the harness-facing graph; changing it +is an ADR-level decision. + +| Bevy release | Status | +| ------------ | --------------------------------------------------------------------------------- | +| `0.19.1` | Current compatibility line; `default-features = false` with `features = ["std"]`. | +| `0.17.3` | Historical probe baseline only; superseded by the `0.19.1` release. | + +*Table 1: Bevy compatibility of the harness crate.* + +The crate declares no Bevy renderer, window, asset, or audio feature; the +resolved graph in a workspace build is a workspace-wide property, because Cargo +unifies features across members built in one invocation. diff --git a/crates/rstest-bdd-harness-bevy/src/lib.rs b/crates/rstest-bdd-harness-bevy/src/lib.rs new file mode 100644 index 0000000..ec5c532 --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/src/lib.rs @@ -0,0 +1,25 @@ +//! Reusable headless Bevy harness scaffolding for `rstest-bdd` behavioural +//! tests. +//! +//! This crate incubates in the Skyjoust workspace but depends on no Skyjoust or +//! Lille code, so it can move to its own repository as a directory move plus a +//! dependency rewire. Game-specific setup belongs in downstream profile types, +//! never here. + +mod profile; + +pub use bevy; +pub use profile::{add_minimal_plugins, minimal_app}; +pub use rstest_bdd_harness::{ + AttributePolicy, + HarnessAdapter, + HarnessError, + HarnessResult, + ScenarioMetadata, + ScenarioRunRequest, + ScenarioRunner, + StdScenarioRunRequest, + StdScenarioRunner, + TestAttribute, +}; +pub use tracing; diff --git a/crates/rstest-bdd-harness-bevy/src/profile.rs b/crates/rstest-bdd-harness-bevy/src/profile.rs new file mode 100644 index 0000000..ecaf400 --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/src/profile.rs @@ -0,0 +1,54 @@ +//! Headless Bevy application configuration. +//! +//! This module owns the smallest plugin set a headless behavioural scenario +//! needs: `App` scaffolding with no window, renderer, asset, or audio stack. +//! `add_minimal_plugins` has exactly the shape design §5's `configure` calls, +//! so `MinimalBevyProfile::configure` becomes two lines that call it at +//! `0.5.1.2`. + +use bevy::{app::App, prelude::MinimalPlugins}; + +/// Adds the minimal headless plugin set to `app`. +/// +/// [`MinimalPlugins`] provides task pools, time, the frame counter, and the +/// schedule runner — no window and no renderer, so the application advances +/// under `cargo test` without a display server. +/// +/// # Example +/// +/// ``` +/// use bevy::app::App; +/// use rstest_bdd_harness_bevy::add_minimal_plugins; +/// +/// let mut app = App::new(); +/// add_minimal_plugins(&mut app); +/// app.update(); +/// ``` +pub fn add_minimal_plugins(app: &mut App) { app.add_plugins(MinimalPlugins); } + +/// Builds a headless Bevy application carrying only the minimal plugin set. +/// +/// This is the harness-free entry point for tests that want an application +/// without a scenario. Each call returns a fresh application whose frame count +/// starts at zero and whose time plugin reads wall-clock time. +/// +/// # Example +/// +/// ``` +/// use bevy::diagnostic::FrameCount; +/// use rstest_bdd_harness_bevy::minimal_app; +/// +/// let mut app = minimal_app(); +/// app.update(); +/// assert_eq!(app.world().resource::().0, 1); +/// ``` +#[must_use = "advance the returned application or inspect its world"] +pub fn minimal_app() -> App { + let mut app = App::new(); + add_minimal_plugins(&mut app); + app +} + +#[cfg(test)] +#[path = "profile_tests.rs"] +mod tests; diff --git a/crates/rstest-bdd-harness-bevy/src/profile_tests.rs b/crates/rstest-bdd-harness-bevy/src/profile_tests.rs new file mode 100644 index 0000000..439e62e --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/src/profile_tests.rs @@ -0,0 +1,47 @@ +//! Unit tests for the headless plugin configuration. + +use bevy::{app::App, diagnostic::FrameCount, time::TimePlugin}; +use googletest::prelude::*; +use rstest::rstest; + +use super::{add_minimal_plugins, minimal_app}; + +#[gtest] +#[rstest] +#[case(0)] +#[case(3)] +fn minimal_app_counts_frames(#[case] ticks: u32) { + let mut app = minimal_app(); + (0..ticks).for_each(|_| app.update()); + expect_that!(app.world().resource::().0, eq(ticks)); +} + +#[gtest] +#[rstest] +fn minimal_app_adds_the_time_plugin() { + let app = minimal_app(); + expect_that!(app.is_plugin_added::(), eq(true)); +} + +#[gtest] +#[rstest] +fn an_unconfigured_app_omits_the_time_plugin() { + let app = App::new(); + expect_that!(app.is_plugin_added::(), eq(false)); +} + +#[gtest] +#[rstest] +fn add_minimal_plugins_matches_minimal_app() { + let mut configured = App::new(); + add_minimal_plugins(&mut configured); + let mut constructed = minimal_app(); + configured.update(); + constructed.update(); + expect_that!(configured.is_plugin_added::(), eq(true)); + expect_that!(constructed.is_plugin_added::(), eq(true)); + expect_that!( + configured.world().resource::().0, + eq(constructed.world().resource::().0) + ); +} diff --git a/crates/rstest-bdd-harness-bevy/tests/extraction_boundary.rs b/crates/rstest-bdd-harness-bevy/tests/extraction_boundary.rs new file mode 100644 index 0000000..ef1b5b6 --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/tests/extraction_boundary.rs @@ -0,0 +1,93 @@ +//! Tripwire for the extraction contract: no game crate may be declared here. +//! +//! The check is textual and direct-only. It sees neither transitive edges nor +//! renamed packages; `cargo tree -p rstest-bdd-harness-bevy -e normal,dev` +//! remains the authority for the constraint as a whole. + +use pretty_assertions::assert_eq; + +/// Crate names this harness must never declare, per the extraction contract. +const FORBIDDEN_CRATES: [&str; 3] = ["skyjoust", "skyjoust-stateright-validator", "lille"]; + +/// Returns the entries of [`FORBIDDEN_CRATES`] declared in `manifest`'s +/// dependency tables. +/// +/// The returned names carry the `'static` lifetime deliberately: they come +/// from the fixed list, not from `manifest`. The scan covers the dependency, +/// development-dependency, and build-dependency tables only, so a `repository` +/// field naming the Skyjoust remote cannot false-positive; and it matches +/// dependency names exactly, so the permitted `skyjoust-test-macros` passes +/// through. +fn forbidden_dependencies(manifest: &str) -> Vec<&'static str> { + let dependency_tables = [ + "[dependencies]", + "[dev-dependencies]", + "[build-dependencies]", + ]; + let mut in_dependency_table = false; + let mut found = Vec::new(); + for line in manifest.lines() { + let trimmed = line.trim(); + if trimmed.starts_with('[') { + in_dependency_table = dependency_tables.contains(&trimmed); + continue; + } + if !in_dependency_table { + continue; + } + let name = trimmed.split_once('=').map_or("", |(name, _)| name.trim()); + if let Some(&forbidden) = FORBIDDEN_CRATES.iter().find(|&&entry| entry == name) { + found.push(forbidden); + } + } + found +} + +#[test] +fn manifest_declares_no_game_crates() { + assert_eq!( + forbidden_dependencies(include_str!("../Cargo.toml")), + Vec::<&str>::new() + ); +} + +#[test] +fn guard_detects_a_directly_declared_game_crate() { + let manifest = r#" +[package] +name = "probe" + +[dependencies] +some-tooling-crate = "1.0" +skyjoust = { path = "../.." } +"#; + assert_eq!(forbidden_dependencies(manifest), vec!["skyjoust"]); +} + +#[test] +fn guard_ignores_game_names_outwith_dependency_tables() { + let manifest = r#" +[package] +name = "probe" +repository = "https://github.com/example/skyjoust" + +[dependencies] +some-tooling-crate = "1.0" + +[dev-dependencies.lille] +version = "0.1" +"#; + assert_eq!(forbidden_dependencies(manifest), Vec::<&str>::new()); +} + +#[test] +fn guard_permits_the_test_macro_crate() { + let manifest = r#" +[package] +name = "probe" + +[dev-dependencies] +skyjoust-test-macros = { path = "../skyjoust_test_macros" } +"#; + assert_eq!(forbidden_dependencies(manifest), Vec::<&str>::new()); +} diff --git a/crates/rstest-bdd-harness-bevy/tests/features/headless_scenario.feature b/crates/rstest-bdd-harness-bevy/tests/features/headless_scenario.feature new file mode 100644 index 0000000..1a9ea2b --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/tests/features/headless_scenario.feature @@ -0,0 +1,6 @@ +Feature: Headless Bevy scaffolding + + Scenario: A minimal application advances one tick + Given a minimal headless Bevy application + When the schedule advances once + Then the frame count reads 1 \ No newline at end of file diff --git a/crates/rstest-bdd-harness-bevy/tests/headless_scenario.rs b/crates/rstest-bdd-harness-bevy/tests/headless_scenario.rs new file mode 100644 index 0000000..6f5cd84 --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/tests/headless_scenario.rs @@ -0,0 +1,47 @@ +//! Behavioural coverage proving `rstest-bdd` drives a headless Bevy app. + +use std::cell::RefCell; + +use bevy::{app::App, diagnostic::FrameCount, time::TimePlugin}; +use googletest::prelude::*; +use rstest::fixture; +use rstest_bdd::StepResult; +use rstest_bdd_harness_bevy::minimal_app; +use rstest_bdd_macros::{given, scenario, then, when}; +use skyjoust_test_macros::allow_fixture_expansion_lints; + +/// Gives `rustc` a rebuild dependency on the feature file; feature-file-only +/// edits do not otherwise invalidate the build. +const _: &str = include_str!("features/headless_scenario.feature"); + +/// Scenario-scoped headless application shared by the steps below. +#[allow_fixture_expansion_lints] +#[fixture] +fn app() -> RefCell { RefCell::new(minimal_app()) } + +#[given("a minimal headless Bevy application")] +fn given_minimal_app(app: &RefCell) -> StepResult<(), String> { + app.try_borrow() + .map(|shared| assert_that!(shared.is_plugin_added::(), eq(true))) + .map_err(|error| error.to_string()) +} + +#[when("the schedule advances once")] +fn when_schedule_advances_once(app: &RefCell) -> StepResult<(), String> { + app.try_borrow_mut() + .map(|mut borrow| borrow.update()) + .map_err(|error| error.to_string()) +} + +#[then("the frame count reads 1")] +fn then_frame_count_reads_one(app: &RefCell) -> StepResult<(), String> { + app.try_borrow() + .map(|shared| { + let observed = shared.world().resource::().0; + assert_that!(observed, eq(1_u32)); + }) + .map_err(|error| error.to_string()) +} + +#[scenario(path = "tests/features/headless_scenario.feature", index = 0)] +fn minimal_app_advances_one_tick(app: RefCell) {} diff --git a/crates/rstest-bdd-harness-bevy/tests/tick_properties.rs b/crates/rstest-bdd-harness-bevy/tests/tick_properties.rs new file mode 100644 index 0000000..36d3b95 --- /dev/null +++ b/crates/rstest-bdd-harness-bevy/tests/tick_properties.rs @@ -0,0 +1,17 @@ +//! Property coverage for the bounded tick invariant: after `n` calls to +//! `App::update`, the frame count is `n`. + +use bevy::diagnostic::FrameCount; +use proptest::prelude::*; +use rstest_bdd_harness_bevy::minimal_app; + +proptest! { + #![proptest_config(ProptestConfig { cases: 32, ..ProptestConfig::default() })] + + #[test] + fn frame_count_tracks_update_calls(ticks in 0_u32..=32) { + let mut app = minimal_app(); + (0..ticks).for_each(|_| app.update()); + prop_assert_eq!(app.world().resource::().0, ticks); + } +} diff --git a/docs/adr/002-crate-layout-and-public-api.md b/docs/adr/002-crate-layout-and-public-api.md index 0cd7e1d..e071995 100644 --- a/docs/adr/002-crate-layout-and-public-api.md +++ b/docs/adr/002-crate-layout-and-public-api.md @@ -93,3 +93,9 @@ with named modules in the file tree, not a multi-crate workspace, until a later ADR records a specific extraction. Roadmap task `1.1.1`, the technical design's runtime ownership table, and the development plan's phase-2 entry all align with this decision. + +[ADR 007](007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md) adds +`rstest-bdd-harness-bevy` as a third member crate — a tooling-facing test +adapter in the same category as the validator exception recorded above. It does +not reopen this ADR's deferral of runtime splits, which continues to govern +*runtime* functionality only. diff --git a/docs/adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md b/docs/adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md new file mode 100644 index 0000000..498a751 --- /dev/null +++ b/docs/adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md @@ -0,0 +1,158 @@ +# 007: In-tree incubation of the Bevy BDD harness crate + +Status: Accepted + +Date: 2026-08-17 + +Accepted: 2026-08-17 + +## Context + +Project Skyjoust is a Cargo workspace: the root `skyjoust` package, the +`skyjoust_stateright_validator` crate, and the `skyjoust_test_macros` crate +added by [ADR 006](006-test-macro-crate-for-fixture-expansion-lints.md). +Roadmap phase `0.5` needs behaviour-driven tests that drive a deterministic, +headless Bevy entity-component system (ECS) application from Gherkin scenarios +through the ordinary `cargo test` harness. The reusable vehicle is a new crate, +`rstest-bdd-harness-bevy`, that plugs a headless Bevy application into the +`rstest-bdd` harness contract. + +Bevy types cross the harness boundary: downstream profile types configure the +same `bevy::app::App` the harness drives, and roadmap `1.2.2` brings Bevy into +the runtime. The harness crate therefore cannot make an independent Bevy +version choice. The version it builds against is a workspace-wide coupling. + +The `rstest-bdd` version situation forces the dependency strategy. The +`0.6.0-beta3` release was published to crates.io on 2026-07-07 and carries the +harness API this work targets: `HarnessAdapter`, `ScenarioRunRequest`, and the +reserved `rstest_bdd_harness_context` fixture key. Upstream `main` has since +moved to an unpublished `0.6.0-beta4` that changes `StepContext::borrow_mut` to +take `&self`, adds a `policy_conformance` module and a `testing` feature, and +renames generated panic text from `initialise` to `initialize`. A caret +requirement on the pre-release line admits `beta4`, so the workspace must pin +exactly until a deliberate compatibility pass validates a later release. + +The harness crate must stay free of Skyjoust and Lille code so it can later +move to its own repository. The roadmap names the extraction target as +`leynos/rstest-bdd-harness-bevy`. + +## Decision + +### Workspace membership + +Add `crates/rstest-bdd-harness-bevy` as a workspace member, growing the +workspace to four members: the root package plus three crates. This does not +reopen [ADR 002](002-crate-layout-and-public-api.md)'s deferral of runtime +crate splits. That decision governs *runtime* functionality, which must stay as +modules inside the runtime crate; the harness is a tooling-facing test adapter, +in the same category as the exception ADR 002 already grants the validator +crate. + +### The extension seam + +State the crate boundary as an extension-seam rule rather than hexagonal +taxonomy: *the profile type is the single extension seam. Every game-specific +plugin, resource, and cleanup hook lives in a downstream implementation of it, +never in this crate.* The harness trait is an extension point owned by +`rstest-bdd`, not a port this crate defines against a domain it owns, so +ports-and-adapters language would strain the design. The durable consequence — +the crate holds no game domain logic — is what keeps game code out of the +extracted repository. + +### Extraction contract and its true cost + +The crate must never depend on a Skyjoust gameplay crate or any Lille crate, +directly or transitively. Test-only tooling is a separate category: +`skyjoust_test_macros` is a permitted development dependency, on the same +footing as `rstest`, and is deliberately excluded from the extracted +repository. The boundary guard matches dependency *names* exactly rather than +by substring, so the permitted `skyjoust-test-macros` near-match passes. + +Extraction is more than a directory move plus a dependency rewire. It also +requires a *configuration transplant*, because workspace lint inheritance does +not survive the move. The extracted repository needs the `[workspace.lints]` +tables, `clippy.toml`, `.rustfmt.toml`, `rust-toolchain.toml`, and the Whitaker +Dylint wiring copied out before it can pass the same gates. + +### Dependency strategy + +- Depend on the published `rstest-bdd` family crates, not git dependencies + against `main`. `0.6.0-beta3` is published, so the roadmap's conditional + git-dependency instruction no longer applies. +- Pin every `rstest-bdd` family crate exactly to `=0.6.0-beta3`, a + maintainer-approved temporary exception to the workspace's implicit-caret + mandate. A caret on a pre-release is not a pin: it admits `0.6.0-beta4`, + which upstream has made source-breaking, and Dependabot automerges daily here. + `Cargo.lock` is load-bearing for this family until an explicitly validated + `0.6.0` final returns the workspace to implicit caret syntax. +- Write ordinary stable requirements with implicit caret syntax, such as + `bevy = { version = "0.19.1", default-features = false, features = ["std"] }`. + The `std` feature is required: without it, `bevy_platform` selects a fallback + `Instant` that passes `_rdtsc()` straight to `Duration::from_nanos`, so + `Time` advances at roughly the timestamp counter frequency rather than + wall-clock. +- Target Bevy `0.19.1` for both the harness and the future Skyjoust runtime. + One Bevy line must resolve across the harness-facing graph (`EP-REQ-003`). +- Declare `tracing` directly and re-export it separately, rather than + re-exporting it through `rstest-bdd-harness`, so this crate's public API does + not expose a version it does not control. + +### Bevy version ownership + +The Bevy release is a workspace-wide coupling, not a local choice. The +maintainer owns the bump. A change is an ADR-level decision that requires a +compatibility pass across every consumer, because Bevy types appear in this +crate's public signatures and in downstream profile types. + +### The headless guarantee + +The crate declares no Bevy renderer, window, asset, or audio features — +`default-features = false` with only `features = ["std"]` enabled. State the +guarantee accurately: *this crate declares no such features; the resolved graph +in a workspace build is a workspace-wide property*, because Cargo unifies +features across members built in one invocation. `make test` and `make lint` +build `--workspace --all-features`. Any other member that later adds `bevy` +must also declare `default-features = false`, or the headless guarantee is lost +for everyone. + +### Dependency-sourcing options + +| Source | Resolution | Stability | Risk | Outcome | +| ------------------------------------------------------------- | ---------------------------------- | -------------------------------- | -------------------------------------- | ----------------------------------------------- | +| Git dependency on `rstest-bdd` `main` | Unpinned head, `0.6.0-beta4` API | Drifts with upstream commits | Source-breaking changes; vendored GPUI | Rejected: `beta3` is published | +| Caret `^0.6.0-beta3` | Admits `beta4` and `0.6.0` final | Depends on Dependabot behaviour | Automerged breaking upgrade | Rejected: a caret on a pre-release is not a pin | +| Exact `=0.6.0-beta3` (**chosen**) | Pinned | Reproducible `Cargo.lock` | Requires a deliberate upgrade pass | Chosen until an explicitly validated `0.6.0` | +| Bevy `0.19.1` caret with `default-features = false` and `std` | One Bevy line across the workspace | Cargo lock keeps it reproducible | Workspace build cost | Chosen; the runtime couples to the same release | + +*Table 1: Dependency-sourcing options for the `rstest-bdd` family and Bevy.* + +## Consequences + +The workspace carries a fourth member whose library depends on Bevy with +default features disabled and pulls the `rstest-bdd` harness contract into +every workspace build. `make test` and `make lint` each compile the added +`0.19.1` graph, and the CI caches grow correspondingly; the measured cost is +recorded in the ExecPlan's `Outcomes & retrospective`. + +[ADR 002](002-crate-layout-and-public-api.md)'s Consequences told readers to +expect exactly two crates. That statement is superseded by this record, and a +forward pointer is added there so the two documents do not disagree. + +The `rstest-bdd` family stays exactly pinned for now. A published `0.6.0` final +is a migration trigger, not proof of compatibility: validate the whole +behavioural suite against it before returning to implicit caret syntax. In the +meantime, a bare `cargo update` or Dependabot automerge resolves nothing for a +pinned pre-release, so `Cargo.lock` churn cannot admit the breaking `beta4`. + +Roadmap task `0.5.1.1` delivers the crate scaffold and its first tests; the +harness types themselves (`BevyScenario`, `BevyHarness`, `BevyProfile`, and +their companions) belong to task `0.5.1.2`. Extraction to +`leynos/rstest-bdd-harness-bevy` stays a directory move plus a dependency +rewire, but only after the configuration transplant documented above, and only +after Skyjoust and Lille each carry one headless scenario through their normal +gates (roadmap `0.5.1.5`). + +Downstream adoption: Skyjoust and Lille each define a profile type outside this +crate that adds `MinimalPlugins` and their own plugins. Game-specific setup +therefore never reaches the reusable crate, and the single Bevy release line is +the only coupling the extracted repository inherits. diff --git a/docs/contents.md b/docs/contents.md index 2383acc..c886ccc 100644 --- a/docs/contents.md +++ b/docs/contents.md @@ -64,11 +64,16 @@ The current Architecture Decision Records (ADRs) live under `docs/adr/`. - [ADR 006: Suppress macro-expansion lints through a test-macro crate](adr/006-test-macro-crate-for-fixture-expansion-lints.md) - accepted decision to hold fixture expansion lint suppression in a test-only procedural-macro crate. +- [ADR 007: In-tree incubation of the Bevy BDD harness crate](adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md) + - accepted decision to incubate `rstest-bdd-harness-bevy` in-tree and pin + the `rstest-bdd` family to `=0.6.0-beta3`. ## Execution plans - [Record the initial runtime crate split as an ADR](execplans/1-1-1-record-runtime-crate-split-as-adr.md) - approval-gated plan for roadmap task `1.1.1`. +- [Add `rstest-bdd-harness-bevy` as a workspace member](execplans/0-5-1-1-add-rstest-bdd-harness-bevy-workspace-member.md) + - execution plan for roadmap task `0.5.1.1`. ## State graph references and artefacts diff --git a/docs/developers-guide.md b/docs/developers-guide.md index 24136fb..3f9da05 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -17,9 +17,11 @@ The validator must stay synchronized with these source documents: [ADR 002](adr/002-crate-layout-and-public-api.md) is the source of truth for the workspace shape. The accepted decision is one runtime crate with strict -internal modules, beside the separate `skyjoust_stateright_validator` crate. -Maintainers should add new runtime functionality as a module inside the runtime -crate, not as a new crate. +internal modules, beside the separate `skyjoust_stateright_validator` crate; +[ADR 007](adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md) adds a +third member crate, `rstest-bdd-harness-bevy`, as tooling in the same category +as the validator exception. Maintainers should add new runtime functionality as +a module inside the runtime crate, not as a new crate. The runtime modules and their responsibilities follow the technical design's runtime ownership table: `game_app`, `core`, `sim`, `terrain`, `stategraphs`, @@ -44,6 +46,47 @@ conditions holds: Record any such extraction in a follow-up ADR before changing `Cargo.toml` workspace members. +### 2.1. The `rstest-bdd-harness-bevy` crate + +The harness crate plugs a headless Bevy application into the `rstest-bdd` +harness contract. Its boundary rule is stated in +[ADR 007](adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md) as an +extension seam: *the profile type is the single extension seam. Every +game-specific plugin, resource, and cleanup hook lives in a downstream +implementation of it, never in this crate.* Skyjoust and Lille each define a +profile type outside the crate once the first deterministic runtime resources +exist (roadmap `0.5.1.4`); the crate itself never acquires game modules. + +The crate incubates here but keeps its extraction contract: no Skyjoust or +Lille dependency, directly or transitively. `tests/extraction_boundary.rs` +trips on a directly declared game crate, and +`cargo tree -p rstest-bdd-harness-bevy -e normal,dev` remains the authority for +the transitive half. + +Writing behavioural scenarios taught this task two estate-specific traps and +one Bevy trap. + +- The `allow-expect-in-tests` setting covers `#[test]` functions only. + `rstest-bdd` step functions are macro-registered free functions, so + `.expect()` inside a step fails `make lint`. Fallible steps return + `StepResult` and propagate `RefCell` borrow failures with `try_borrow` and + `try_borrow_mut`, mapping errors instead of panicking. See + `tests/headless_scenario.rs` for the pattern. +- A feature-file-only edit does not invalidate a `cargo test` build: only + Rust sources and `include_str!` dependencies are fingerprinted. Bind the + feature file with `const _: &str = include_str!("features/...");` so a + changed scenario cannot pass stale against an old compiled step table. The + ExecPlan's Milestone 4 A/B test proved the guard load-bearing. +- `googletest`'s `expect_that!` panics without a `#[gtest]` test context, so + unit tests annotate `#[gtest]` above `#[rstest]`. `rstest-bdd` step functions + have no such context and use `assert_that!`, which panics directly. +- Bevy's `MinimalPlugins` includes `ScheduleRunnerPlugin`, whose own `run` + method loops forever. Scenarios must call `App::update` explicitly (via the + fixture or a step) and must never let the schedule runner drive the loop. + +The fixture-expansion lint trap is documented in §7.3; it applies to the +behavioural fixture exactly as the search-based tests in that section describe. + ## 3. Validator module structure The `skyjoust-stateright-validator` crate keeps domain logic in small modules: @@ -207,11 +250,10 @@ cargo run -p skyjoust-stateright-validator --bin validate_trace \ ``` Set `SKYJOUST_VALIDATOR_DEBUG=1` during debug builds to emit a -`tracing::debug!` event for each transition attempt during depth-first -search. `validate_trace` and the Explorer example both install a -stderr-writing `tracing_subscriber`, so the events are visible when -running either; a caller embedding the library elsewhere must install -its own subscriber to observe them. +`tracing::debug!` event for each transition attempt during depth-first search. +`validate_trace` and the Explorer example both install a stderr-writing +`tracing_subscriber`, so the events are visible when running either; a caller +embedding the library elsewhere must install its own subscriber to observe them. ## 7. Lint baseline @@ -317,37 +359,35 @@ run under the pinned nightly automatically; rustup resolves the toolchain from ## 8. Fast development builds -`make dev-build` and `make dev-test` offer an opt-in, faster iteration loop -for local debug work: `dev-build` compiles debug binaries and `dev-test` -runs the test suite, both using the Cranelift codegen backend and the -`mold` linker configured in `tools/dev-fast/config.toml`. +`make dev-build` and `make dev-test` offer an opt-in, faster iteration loop for +local debug work: `dev-build` compiles debug binaries and `dev-test` runs the +test suite, both using the Cranelift codegen backend and the `mold` linker +configured in `tools/dev-fast/config.toml`. The `DEV_FAST_CONFIG` variable names that fragment, defaulting to `tools/dev-fast/config.toml`; both targets pass it to Cargo explicitly with -`--config "$(DEV_FAST_CONFIG)"`. Cargo never auto-discovers this fragment — -it takes effect only when a target invokes it directly. +`--config "$(DEV_FAST_CONFIG)"`. Cargo never auto-discovers this fragment — it +takes effect only when a target invokes it directly. -Using the fragment requires a nightly toolchain, since the Cranelift -codegen backend is unstable. On Linux it also requires the `mold` linker -on `PATH`; the fragment gates the `-fuse-ld=mold` flag behind a -`target_os = "linux"` `cfg` table, so other platforms fall back to their -default linker. +Using the fragment requires a nightly toolchain, since the Cranelift codegen +backend is unstable. On Linux it also requires the `mold` linker on `PATH`; the +fragment gates the `-fuse-ld=mold` flag behind a `target_os = "linux"` `cfg` +table, so other platforms fall back to their default linker. Never copy the fragment's contents into `.cargo/config.toml`. Cargo auto-discovers that file and applies it to every invocation, which would -silently degrade release, coverage, and verification builds to the faster -but less optimizing backend. Keep the fast-build configuration isolated in +silently degrade release, coverage, and verification builds to the faster but +less optimizing backend. Keep the fast-build configuration isolated in `tools/dev-fast/config.toml` and reach it only through `make dev-build` and `make dev-test`, or through the standard targets described next. -Skyjoust's own extra fact, beyond the general dev-fast contract above: per -§7, the standard `build`, `test`, `lint`, and `typecheck` targets already -pass `--config "$(DEV_FAST_CONFIG)"` to every `cargo` invocation they make. -Dev-fast is therefore skyjoust's standard development path, not only an -opt-in one; `make dev-build`/`make dev-test` remain useful for a -build/test cycle that skips the other standard targets' formatting and -lint checks. The one exception is `lint`'s Whitaker Dylint invocation: -Whitaker runs its own dylint driver under a separately pinned toolchain, -outside rustup's toolchain-file auto-install mechanism, so nothing -guarantees that toolchain has the Cranelift component the fragment -selects — the fragment is deliberately not passed there. +Skyjoust's own extra fact, beyond the general dev-fast contract above: per §7, +the standard `build`, `test`, `lint`, and `typecheck` targets already pass +`--config "$(DEV_FAST_CONFIG)"` to every `cargo` invocation they make. Dev-fast +is therefore skyjoust's standard development path, not only an opt-in one; +`make dev-build`/`make dev-test` remain useful for a build/test cycle that +skips the other standard targets' formatting and lint checks. The one exception +is `lint`'s Whitaker Dylint invocation: Whitaker runs its own dylint driver +under a separately pinned toolchain, outside rustup's toolchain-file +auto-install mechanism, so nothing guarantees that toolchain has the Cranelift +component the fragment selects — the fragment is deliberately not passed there. diff --git a/docs/execplans/0-5-1-1-add-rstest-bdd-harness-bevy-workspace-member.md b/docs/execplans/0-5-1-1-add-rstest-bdd-harness-bevy-workspace-member.md new file mode 100644 index 0000000..6e0fce2 --- /dev/null +++ b/docs/execplans/0-5-1-1-add-rstest-bdd-harness-bevy-workspace-member.md @@ -0,0 +1,2182 @@ +# Add `rstest-bdd-harness-bevy` as a workspace member + +This ExecPlan (execution plan) is a living document. The sections `Constraints`, +`Tolerances`, `Risks`, `Progress`, `Surprises & discoveries`, `Decision log`, +`Outcomes & retrospective`, `Conformance basis`, and `Verification plan` must +be kept up to date as work proceeds. + +Status: COMPLETED — PENDING REMOTE CI + +Approval gate: this plan must be approved before implementation begins. Do not +treat silence as approval. The five choices in +`Decisions resolved before approval` are settled, but settling them does not +itself authorize execution. The maintainer authorized execution on 2026-08-17 +by directing that the planned functionality be implemented. All six milestones +are delivered; the branch awaits the authorized push to update pull request 51. +The 2026-08-21 rebase and local acceptance evidence are complete; do not treat +the plan as fully accepted until pull request #51 has a clean merge state +and its remote checks have run. + +Roadmap task: `0.5.1.1` in [the Skyjoust roadmap](../roadmap.md). + +## Purpose / big picture + +Skyjoust needs behavioural tests that drive a deterministic, headless Bevy +entity-component system (ECS) without dragging a renderer, a window, or a +graphics processing unit (GPU) into `cargo test`. The eventual vehicle is a +reusable harness crate, `rstest-bdd-harness-bevy`, that plugs a headless Bevy +application into the `rstest-bdd` harness contract. That harness must stay free +of Skyjoust and Lille code so it can later move to its own repository as a +directory move plus a dependency rewire. + +This plan delivers the first step only: the crate exists as a workspace member, +compiles headlessly, carries the estate lint baseline, and proves — with +running tests — that the load-bearing dependency choices work together on this +repository's pinned toolchain and its Cranelift build configuration. It stops +short of the harness types themselves (`BevyScenario`, `BevyHarness`, +`BevyProfile`, `BareBevyProfile`, `MinimalBevyProfile`, `BevyAttributePolicy`); +the roadmap assigns those names to task `0.5.1.2`. + +After this change a developer can run one command and watch a headless Bevy +application advance a fixed tick from inside a Gherkin scenario: + +```bash +cargo test -p rstest-bdd-harness-bevy +``` + +and see, among the passing tests, a behaviour-driven development (BDD) scenario +named `minimal_app_advances_one_tick` that drives a real `bevy::app::App` +through one `Update` schedule pass. + +The observable outcome is: the workspace has a third crate; that crate has no +path to Skyjoust or Lille; `make check-fmt`, `make lint`, and `make test` all +pass; and the crate's own tests demonstrate a headless Bevy tick driven by +`rstest-bdd`. + +## Decisions resolved before approval + +The following choices were resolved by the maintainer on 2026-08-17. They are +requirements for implementation, not defaults that an implementer may revisit +without escalating. + +1. Target Bevy `0.19.1`. The harness and the future Skyjoust runtime must use + the same release because Bevy types cross the harness boundary. The earlier + `0.17.3` probes remain historical evidence only; Milestone 0 must repeat the + compatibility and cost probes against `0.19.1` before implementation. +2. Write ordinary stable dependency requirements using Cargo's implicit caret + syntax, for example `0.19.1`, never the redundant spelling `^0.19.1`. +3. Pin every `rstest-bdd` family crate to `=0.6.0-beta3` for now. A later beta + or `0.6.0` final may break the pre-release API, so upgrading requires a + deliberate compatibility pass. If the next release is `0.6.0` final, move + back to implicit caret syntax only after that pass is green. +4. `rstest-bdd` step functions are not `#[test]` functions, so the workspace's + test allowance does not permit `.expect()` in them. Propagate fallible work + through `Result` and `?`; where it stays clear, prefer functional + transformations over imperative unwrap-and-mutate sequences. +5. [Pull request #6](https://github.com/leynos/skyjoust/pull/6) updates the root + package from `rstest 0.18` to the latest public release. Watch it until + automerge completes, then rebase this stack onto `main` before Milestone 2. + Reconcile the new crate's `rstest` requirement with the merged root + requirement after the rebase rather than planning around two versions. + +## Constraints + +These are hard invariants. If satisfying the objective would require violating +one, stop and escalate rather than working around it. + +- The crate must not depend on `skyjoust`, `skyjoust-stateright-validator`, or + any Lille crate, directly or transitively. This is the extraction contract + from [the harness design](../rstest-bdd-harness-bevy-design.md) §11. The + contract is about *game* code: gameplay rules, runtime state resources, and + validator types must never reach this crate. Test-only tooling is a separate + category — `skyjoust-test-macros` is a permitted development dependency, on + the same footing as `rstest` or `googletest`, and is listed in the extraction + note so the eventual move rewires it deliberately. Note the trap this creates: + `skyjoust-test-macros` contains the substring `skyjoust`, so the boundary + guard must match dependency *names* exactly rather than by substring, or it + will reject a dependency the contract allows. +- The crate's own manifest must declare Bevy with `default-features = false` + and enable no feature beyond `std` (see `Decision log`). Cargo unifies + features across workspace members built in one invocation, and `make test` and + `make lint` both build `--workspace --all-features`. This constraint + therefore bounds what the crate *requests*, not what a workspace build + resolves. Any other member that later adds `bevy` must also declare + `default-features = false`, or the headless guarantee is lost for everyone. +- The harness crate's Bevy release version is a workspace-wide coupling, not a + local choice. It must equal the version the Skyjoust runtime will use at + roadmap `1.2.2`; this plan fixes that version at `0.19.1`. Changing it is an + ADR-level decision. +- No Skyjoust gameplay profile, validator trace type, or runtime state resource + may be checked into the harness crate. Downstream profiles live in the + consuming crate (design §8). +- `crates/rstest-bdd-harness-bevy/Cargo.toml` must carry + `[lints]` with `workspace = true`, per + [the developer's guide](../developers-guide.md) §7.1. +- `unsafe_code` stays forbidden; `missing_docs` and `missing_crate_level_docs` + stay denied. Every module opens with a `//!` comment and every public item + carries a `///` comment with a worked example, per `AGENTS.md`. +- `clippy::doc_markdown` runs at pedantic level and is promoted to an error by + `-D warnings`. Every type and item name in a `///` or `//!` comment — + `MinimalPlugins`, `FrameCount`, `TimePlugin`, `BevyProfile`, and so on — must + be wrapped in backticks. +- No source file exceeds 400 lines (`AGENTS.md`). +- Do not modify `crates/skyjoust_stateright_validator/`, `src/`, or + `tests/makefile_contract.rs`. +- Do not add or rename Makefile targets. `tests/makefile_contract.rs` asserts + that the `build`, `test`, `lint`, and `typecheck` recipes each pass + `--config tools/dev-fast/config.toml`; leaving the Makefile alone keeps that + contract intact. +- `typos.toml` is generated and must not be hand-edited, but it must be + *regenerated* whenever `typos.local.toml` changes. `make spelling-config` + checks the committed `typos.toml` against the sources and gates + `make markdownlint`, so a stale `typos.toml` fails the documentation gates. +- Proptest regression files under + `crates/rstest-bdd-harness-bevy/tests/proptest-regressions/` are committed, + not ignored. They are shrunk counter-examples, not scratch. +- Prose is British English with Oxford spelling (`-ize`, `-yse`, `-our`), + wrapped at 80 columns; code fences wrap at 120 columns. + +## Tolerances (exception triggers) + +- Scope: if the change touches more than 22 files, stop and escalate. Table 2 + is the authoritative file manifest to measure against. +- Public interface: this plan introduces exactly two public functions plus the + re-export block described in `Interfaces and dependencies`. Anything named in + design §§4-7 — `BevyScenario`, `BevyHarness`, `BevyProfile`, + `BareBevyProfile`, `MinimalBevyProfile`, `BevyAttributePolicy` — belongs to + roadmap task `0.5.1.2`; if one appears necessary here, stop and escalate. +- Dependencies: the dependency set in `Interfaces and dependencies` is fixed. + If any additional crate is required, stop and escalate. +- Bevy features: `default-features = false` with `features = ["std"]` is the + agreed declaration. If a further feature proves necessary, stop and escalate. +- `rstest-bdd` version: if `0.6.0-beta3` proves insufficient and a git + dependency on `main` becomes necessary, stop and escalate. +- Iterations: if a gate still fails after three fix attempts, stop and escalate + with the log path recorded in `Progress`. Before counting an attempt, check + whether two gates are contradicting one another — `check-fmt` and `lint` can + each reject the other's fix (see `Risks`). +- Recurring cost: if a full `make all` on a warm cache exceeds fifteen minutes, + or `target/` exceeds 6 GB, stop and escalate. The crate would then need its + own continuous integration (CI) job, or extraction sooner than `0.5.1.5` + assumes. +- Time: if any milestone exceeds two hours of work, stop and escalate. Time + spent blocked on the shared Cargo package-cache lock does not count. +- Ambiguity: if two readings of a roadmap or design statement would produce + materially different crates, stop and present the options. + +## Risks + +- Risk: `rstest`'s `#[fixture]` expansion re-wraps the function body, so a + single-expression fixture body trips `unused_braces` under `-D warnings`; + `.rustfmt.toml`'s `fn_single_line = true` then reformats any multi-line body + straight back into the failing form. `make check-fmt` and `make lint` demand + mutually exclusive formulations. Severity: high. Likelihood: certain + (observed). Mitigation: retired by the layer beneath this one in the stack, + which adds `crates/skyjoust_test_macros` and its + `allow_fixture_expansion_lints` attribute — the estate-mandated approach, + mirroring `weaver-test-macros`. Apply the attribute directly above + `#[fixture]` and leave the fixture in its natural single-expression form. See + [ADR 006](../adr/006-test-macro-crate-for-fixture-expansion-lints.md) + and [the developer's guide](../developers-guide.md) §7.3. Verified clean + under both gates. + +- Risk: in the `0.17.3` probe, omitting Bevy's `std` feature replaced + `std::time::Instant` with `bevy_platform::time::fallback::Instant`, whose + x86-64 getter reads `core::arch::x86_64::_rdtsc()` and passes the raw tick + count to `Duration::from_nanos`. `Time` then advances at roughly the + timestamp counter frequency rather than wall-clock, and `Time` and + `Time` derive from it. Severity: high. Likelihood: not yet measured for + `0.19.1`. Mitigation: begin with `features = ["std"]`, then repeat the + clock-source and resolved-feature checks against `0.19.1`. The decision is + discharged only if real time remains correct without enabling rendering, + windowing, or the multi-threaded executor. + +- Risk: `clippy::expect_used` is denied estate-wide, and + `allow-expect-in-tests = true` in `clippy.toml` does **not** cover free + functions in `tests/*.rs` that are not `#[test]`-annotated. `rstest-bdd` step + functions are exactly such functions, so `.expect()` inside a step fails + `make lint`. Severity: medium. Likelihood: high (observed). Mitigation: + scenario state comes from an `rstest` fixture holding a `RefCell` + directly. Steps use `try_borrow` or `try_borrow_mut`, return a `StepResult`, + and propagate borrow failures instead of panicking. Prefer `map` and + `map_err` when that keeps the step linear and readable; use `?` when a + functional chain would obscure the assertion. + +- Risk: `^0.6.0-beta3` is not a pin. It admits `0.6.0-beta4` and `0.6.0` + final, and `beta4` changes `StepContext::borrow_mut` to take `&self`. A bare + `cargo update`, or Dependabot's daily run with automerge, would break every + scenario in the workspace. Severity: high. Likelihood: medium. Mitigation: + exact `=0.6.0-beta3` requirements, approved as a temporary exception to the + workspace's implicit-caret policy, plus a note in ADR 007 that `Cargo.lock` + is load-bearing for the `rstest-bdd` family until an explicitly validated + stable upgrade. + +- Risk: editing only a `.feature` file does not invalidate the build, so a + changed scenario can appear to pass while the compiled step table is stale. + Severity: medium. Likelihood: high. Mitigation: the scenario binding carries + an `include_str!` of the feature file, the idiom used by `rstest-bdd`'s own + fixtures. Milestone 4 proves the guard is load-bearing with an A/B test + rather than a single observation. + +- Risk: the crate adds a large graph to every workspace build. The discarded + Bevy `0.17.3` baseline resolved 139 crates for normal dependencies and 279 + including development dependencies; the `0.19.1` cost is not yet measured. + `make lint` compiles it three times (`cargo doc`, `cargo clippy`, and the + Whitaker Dylint driver, the last under its own toolchain with no Cranelift), + and `make test` a fourth. CI caches no Cargo artefacts beyond the two + shared-action caches. Severity: low, revised down after measurement. + Likelihood: certain. Mitigation: repeat the measurements against `0.19.1` + during the Milestone 0 follow-up and record the equivalent figures for the + real workspace in `Outcomes & retrospective`. + +- Risk: under the discarded `0.17.3` baseline, `target/` reached 2.2 GB for + this crate alone. Both CI caches — the `setup-rust` debug-target cache and + the coverage action's whole-target cache — grow accordingly, against GitHub's + 10 GB per-repository cap. Cache thrash degrades every run to cold without + turning anything red. Severity: medium. Likelihood: medium. Mitigation: + record the measured target size in `Outcomes & retrospective`. If the two + caches together approach the cap, raise a follow-up to narrow the coverage + job's cached path. + +- Risk: `make fmt` runs `cargo +nightly fmt` (the floating nightly) while + `make check-fmt` runs `cargo fmt` (the toolchain-file-pinned nightly). With + `unstable_features`, `wrap_comments`, `format_strings`, and + `format_code_in_doc_comments` all enabled, the two can disagree — and this + plan adds doc comments carrying worked examples, which is exactly what + `format_code_in_doc_comments` rewrites. Severity: low. Likelihood: low. + Mitigation: in Milestone 5, run `mdformat-all` and `cargo fmt --all` (pinned) + separately rather than `make fmt`, so the formatting that lands is the + formatting `make check-fmt` will accept. + +- Risk: `googletest`'s `expect_that!` requires an active test context and + panics with a no-test-context message when the test is not annotated + `#[gtest]`. Severity: low. Likelihood: high (observed). Mitigation: unit + tests use `#[gtest]` above `#[rstest]`; step functions use `assert_that!`, + which panics directly and needs no context. + +- Risk: the current branch still sees the workspace root package at + `rstest = "0.18"`, while `rstest-bdd 0.6.0-beta3` requires `rstest 0.26.1`. + Pull request #6 is intended to remove that split, but implementing before it + automerges would bake a transient duplicate into this stack. Severity: + medium. Likelihood: certain until pull request #6 merges. Mitigation: watch + pull request #6, then rebase every branch in the stack onto `main` before + Milestone 2. After the rebase, verify the merged root requirement and use the + same implicit-caret requirement in this crate when it remains compatible with + `rstest-bdd 0.6.0-beta3`; otherwise stop and escalate instead of carrying two + versions deliberately. + +## Progress + +Next action: Milestone 2 — observe and commit the red state. Milestone 1 is +committed (`11559e8 Record the Bevy BDD harness crate decision as ADR 007`). +The Bevy `0.19.1` compile, feature, clock, Cranelift, and cost evidence is +collected by the Milestone 2/3 build rather than by a separate probe, because +the crate itself is the `0.19.1` test vehicle; the results are recorded in +`Surprises & discoveries` as they surface. + +Last green gate: `mdformat-all`, `make markdownlint`, `make nixie`, and +`git diff --check` against the Milestone 1 tree, run 2026-08-17. + +- [x] (2026-08-15) Milestone 0: orientation and evidence gathering. Six probes + run against Bevy `0.17.3`; findings in `Surprises & discoveries` and + `Artefacts and notes` are retained as historical evidence. +- [x] (2026-08-17) Milestone 0 follow-up: align the stack and validate the + chosen baseline. + - [x] Watch pull request #6 until automerge completes. Merged + 2026-08-16T22:25:57Z (`5f5bc7c Bump rstest from 0.18.2 to 0.26.1 (#6)`). + - [x] Rebase the branch stack onto `main`. The four ExecPlan commits were + replayed onto `origin/main` without conflicts; the lower layer's + `skyjoust-test-macros` content is already merged as `4a895dc` (#53). + - [x] Confirm the merged root `rstest` requirement and reconcile the new + crate's development requirement with it. The merged root and validator + manifests both carry `rstest = "0.26"`. The new crate's dev requirement + in `Interfaces and dependencies` is reconciled to the same implicit + caret line (`0.26`), which remains compatible with `rstest-bdd + 0.6.0-beta3` (which expects `rstest 0.26.1`). + - [x] Repeat the API, feature, headless-graph, Cranelift, test, lint, and + cost probes against Bevy `0.19.1`. Discharged by the Milestone 2/3 + build; evidence is recorded in `Surprises & discoveries` and + `Artefacts and notes`, and Table 1 below carries the `0.19.1` + resolution figures. + - [x] Update all historical expectations in this plan with the new evidence. + The 0.17.3 probe figures remain labelled historical and the `0.19.1` + figures stand beside them. +- [x] (2026-08-17) Milestone 1: record the decision. + - [x] Write the new ADR 007. + - [x] Amend the harness design document §§3, 9, 13 and its layout block. + - [x] Add a forward pointer from ADR 002. + - [x] Index ADR 007 and this ExecPlan in `docs/contents.md`. + - [x] Documentation gates green. +- [x] (2026-08-17) Milestone 2 (red): scaffold and failing tests. + - [x] Add the workspace member. + - [x] Write the crate manifest. + - [x] Write `src/lib.rs` with the module declaration but no re-export of it. + - [x] Write `src/profile.rs` (module comment only) and `src/profile_tests.rs`. + - [x] Observe the red state; record the exact error text. + - [x] `make typecheck` as a whole-graph Cranelift smoke test. +- [x] (2026-08-17) Milestone 3 (green): implement `src/profile.rs`; export its + two functions. +- [x] (2026-08-17) Milestone 4: behavioural, property, and boundary coverage. + - [x] `tests/features/headless_scenario.feature`. + - [x] `tests/headless_scenario.rs`. + - [x] Clippy immediately after the scenario binding first compiles. + - [x] `tests/tick_properties.rs`. + - [x] `tests/extraction_boundary.rs`. + - [x] A/B-prove the feature-file rebuild guard. +- [x] (2026-08-17) Milestone 5: documentation. + - [x] Crate `README.md` including the Bevy compatibility table. + - [x] `docs/repository-layout.md`. + - [x] `docs/developers-guide.md`. + - [x] `docs/roadmap.md` — tick `0.5.1.1`, correct the stale sub-bullet. +- [x] (2026-08-17) Milestone 6: full gate run, measurements, push, draft + pull request. + - [x] Full gate run green: `make check-fmt`, `make check-state-graphs`, + `make markdownlint`, `make lint`, `make test`, and `make nixie` all + pass via the `scrutineer` sub-agent (nextest 89/89 plus all doctests; + hold-up numbers recorded in `Outcomes & retrospective`). + - [x] Measurements recorded in `Outcomes & retrospective`. + - [x] CodeRabbit review cleared. `coderabbit review --agent` completed with + zero findings across 19 reviewed files. + - [x] PR title confirmed free of the plan-draft "Plan: " prefix; PR #51 body + rewritten for the implementation deliverable; the Lody session titles + updated. + - [x] (2026-08-21) Rebase onto `origin/main`; preserve `main`'s conflicted + `Cargo.lock`, then regenerate it from the final manifests. The rebuilt + lock resolves `bevy 0.19.1` and every inspected `rstest-bdd` package at + `0.6.0-beta3`. + - [x] (2026-08-21) Re-run all local gates: `make check-fmt`, + `make check-state-graphs`, `make markdownlint`, `make typecheck`, + `make lint`, `make test` (89 passed), and `make nixie`. + - [x] (2026-08-21) Re-run the isolated and workspace graph checks, focused + 13-test crate suite, and feature-file freshness A/B. Both graphs are + headless, the isolated extraction graph is clean, and only the guarded + feature-only edit fails. + - [ ] Publish the rebased branch with `git push --force-with-lease` and wait + for pull request #51 to become mergeable with non-skipped remote CI. + +## Surprises & discoveries + +- Observation: `rstest-bdd` `0.6.0-beta3` was published to crates.io on + 2026-07-07, along with `rstest-bdd-harness`, `rstest-bdd-macros`, + `rstest-bdd-harness-tokio`, and `rstest-bdd-harness-gpui` at the same + version. The roadmap's instruction to use git dependencies against `main` + "until v0.6.0-beta3 is published" is therefore already satisfied. Evidence: + the crates.io versions endpoint lists `0.6.0-beta3` created at + `2026-07-07T23:13:10Z`; `rstest-bdd-harness 0.6.0-beta3` at + `2026-07-07T23:12:46Z`. Impact: this plan uses published requirements, not + git dependencies. The roadmap sub-bullet and design §9 are amended + accordingly. + +- Observation: `rstest-bdd`'s `main` branch has moved to an unpublished + `0.6.0-beta4` (head commit `12b9357`, 2026-08-14), well past the commit + `21b67a4` cited in the design document. That version adds a + `policy_conformance` module, a `testing` feature exposing `FailingHarness`, + and guard-based fixture borrowing. It also changes the generated panic text + from `initialise` to `initialize`. Evidence: `main`'s harness crate root + exports `policy_conformance` and a feature-gated `FailingHarness`; the + extracted `beta3` archive exports neither and has no features table at all. + Impact: under `beta3`, requesting the `testing` feature will not resolve, and + the policy-conformance helper is unavailable. Neither is needed here; + `0.5.1.3` must plan around them. Recorded in the design document with an + explicit migration trigger. + +- Observation: in the earlier Bevy `0.17.3` probe, + `default-features = false` alone silently degraded timekeeping. + `bevy_platform`'s time module selects `std::time` only when its `std` + configuration predicate holds; otherwise it uses a fallback whose x86-64 + getter passes `_rdtsc()` straight to `Duration::from_nanos` — raw timestamp + counter ticks reinterpreted as nanoseconds. `TimePlugin` calls the clock + unconditionally. Evidence: `bevy_platform-0.17.3/src/time/mod.rs` selects the + fallback module in the non-`std`, non-web branch; `src/time/fallback.rs` + contains the `_rdtsc()` arm. Impact: the plan retains `features = ["std"]`, + but the evidence cannot be projected onto the newly selected `0.19.1` + release. Repeat the source and resolved-feature checks in Milestone 0; + escalate if `0.19.1` needs a different feature declaration to preserve real + time without enabling a renderer or window. + +- Observation: `clippy.toml`'s `allow-expect-in-tests` setting does not exempt + non-`#[test]` free functions inside `tests/*.rs`. A step function calling + `.expect()` fails Clippy with warnings denied. Evidence: a probe using a + thread-local `Option` slot and `.expect()` in a `#[when]` step produced two + `expect_used` errors. Impact: the behavioural test uses an `rstest` fixture + holding the application directly. Its steps return `StepResult` and propagate + `RefCell` borrow failures. Published `rstest-bdd 0.6.0-beta3` supports + `Result`-returning steps, so this is the normal error path rather than a + workaround. The same trap will recur in `0.5.1.2`; it is documented in the + developer's guide. + +- Observation: a single-expression `#[fixture]` body trips `unused_braces` + under denied warnings, and `fn_single_line` reformats the multi-line repair + straight back to the failing form. Evidence: + `error: unnecessary braces around block return value` at the fixture, with + `-D unused-braces implied by -D warnings`; a plain non-fixture function with + the same shape does not fire. Impact: this is a known `rstest` issue with an + estate-mandated remedy, not a problem for this plan to solve locally. The + layer beneath this one in the stack adds `crates/skyjoust_test_macros` and its + `allow_fixture_expansion_lints` attribute, mirroring `weaver-test-macros` in + `leynos/weaver`. An earlier draft of this plan proposed a local `let`-binding + workaround; that has been withdrawn, because it would have spread a bespoke + idiom across every fixture the project writes rather than fixing the cause + once. Verified clean under both Clippy and `cargo fmt --check` with the + attribute applied and the fixture left in its natural form. + +- Observation: `googletest 0.14.3`'s `expect_that!` requires the `#[gtest]` + attribute; without it every assertion panics reporting no test context. + Evidence: four unit tests annotated only `#[rstest]` failed that way; adding + `#[gtest]` above `#[rstest]` made all four pass. Impact: unit tests use + `#[gtest]` plus `#[rstest]`; step functions use `assert_that!`. + +- Observation: the constructors proposed in the first draft could not become + what the design says they become. Design §5 declares a `configure` that takes + `&mut App` and returns a harness result — it mutates a borrowed application — + whereas a constructor builds and returns one. Evidence: design §5's trait + definition, compared with the draft's `pub fn minimal_app() -> App`. Impact: + the public surface was reshaped. `add_minimal_plugins` takes `&mut App`, + which is the shape `configure` calls, so `MinimalBevyProfile::configure` + becomes two lines that call it. `bare_app` was dropped entirely: a plain + `App::new()` needs no wrapper, and `BareBevyProfile::configure` has an empty + body, so there was nothing to seed. + +- Observation: in the earlier Bevy `0.17.3` probe, `App::new()` was not + plugin-free. It already added `MainSchedulePlugin` and registers a + message-update system in `First`. Evidence: `bevy_app-0.17.3/src/app.rs`. + Impact: re-check this contract against `0.19.1` before retaining the negative + test. If the contract still holds, keep the test named for what it checks — + that an unconfigured application omits `TimePlugin` — rather than claiming + `App::new()` adds no plugins. + +- Observation: the first draft's build-cost figure of 98 crates was the + Bevy-only probe measured for normal dependencies. The full `0.17.3` probe + resolved 139 crates for normal dependencies and 279 including development + dependencies. Evidence: + `cargo tree --workspace -e normal --prefix none | sort -u | wc -l` and the + `-e normal,dev` equivalent, run against the full shape probe. Impact: the + build-cost risk is restated with measured wall-clock rather than crate counts. + +- Observation: the CI coverage action builds with *default* features via + `cargo llvm-cov nextest`, and nextest does not run doctests. Evidence: the + `generate-coverage` action's Rust runner script in `leynos/shared-actions`. + Impact: doctests contribute nothing to the codecov figure. The patch target + is still met, but by the unit tests — the first draft gave the wrong reason. + +- Observation: `docs/rstest-bdd-users-guide.md` and + `docs/ortho-config-users-guide.md`, cited in the earlier `1.1.1` execplan, do + not exist in this repository. Evidence: repository-wide glob for both names + returns nothing. Impact: this plan cites the upstream `rstest-bdd` users' + guide by uniform resource locator (URL) instead, and does not create either + local file. + +- Observation: the selected Bevy `0.19.1` graph is smaller than the discarded + `0.17.3` baseline, at 121 normal crates and 267 with development + dependencies, measured against the historical 139 and 279. Evidence: + `cargo tree -p rstest-bdd-harness-bevy -e normal --prefix none | sort -u | + wc -l` + and the dev-inclusive equivalent. Impact: both `EP-INV-002` headless queries + and the `EP-INV-001` transitive extraction query print the expected clean + result, and a single `bevy v0.19.1` line remains (`EP-REQ-003`). + `bevy-window` appears in the *lockfile* but is a non-Linux-target dependency: + it is absent from the reachable Linux normal-dependency graph, so both + headless queries pass. The 0.19.1 figures replace the historical `0.17.3` + table entries. + +- Observation: since the lexical crate files were pre-created during a + bash-classifier outage, the Milestone 2 red run reported the expected + `profile_tests.rs` grouped diagnostic and two further `E0432`s from the + not-yet-implemented `minimal_app` re-export in the pre-created integration + test files. The red-state gate is satisfied: the error names exactly the two + missing symbols, and no other cause appears in the build output. + +- Observation: a background Cargo-aware process (rust-analyzer) resolved the + workspace while the new manifest was being created and wrote a 335-package + `Cargo.lock` superset that included `bevy-window` as a reachable package. The + file was restored from `main` and the Milestone 2 build regenerated the + authoritative 227-package lockfile from the crate's actual manifest. Impact: + the lockfile must not be trusted to `cargo metadata` from a watcher; + regenerate it deliberately with the milestone build. + +- Observation: the cold Cranelift resolve plus lock plus compile of the full + `0.19.1` graph and the `rstest-bdd` family took 40 seconds wall-clock on this + six-core host, counting the two package-cache lock waits. Evidence: the + Milestone 2 red run's own elapsed time. Impact: the build-cost tolerance is + not stressed; the warm runs in Milestones 3-6 will confirm the repeat cost. + +- Observation: Milestone 3's unit tests discharge two `0.19.1` contracts the + plan had to re-check: `App::new()` still omits `TimePlugin` (the negative + test passes), and `MinimalPlugins` still includes `FrameCountPlugin` + (updating advances the frame counter). Evidence: the five unit tests and the + two doctests pass. Impact: the retained test names describe what they check. + +- Observation: `clippy::double_must_use` objects to a bare `#[must_use]` on + `minimal_app::() -> App` because Bevy's `App` is itself `#[must_use]`. + Evidence: `make lint` flags profile.rs and suggests "either add some + descriptive message or remove the attribute". Impact: the attribute carries a + message — + `#[must_use = "advance the returned application or inspect its world"]` — + which keeps the plan's requirement and satisfies the estate deny. + +- Observation: the ExecPlan's verbatim `headless_scenario.rs` snippet trips + `clippy::shadow_reuse` under the current workspace baseline: the closure + parameters `|app|`, `|mut app|`, and their inner `let` shadow the step + function's own `app` parameter. Evidence: `make lint` lists three + `shadow_reuse` diagnostics against the snippet as written. Impact: the + closures are renamed (`shared`, `borrow`) without changing behaviour; the + snippet in this plan is amended to match. + +## Decision log + +- Decision: depend on published `rstest-bdd` `0.6.0-beta3` crates rather than + git dependencies against `main`. Rationale: the roadmap's git-dependency + instruction was explicitly conditional on `beta3` not yet being published; it + now is. Published crates keep `Cargo.lock` stable and avoid pulling + `rstest-bdd`'s vendored GPUI tree into this workspace's resolution. The later + APIs are not needed here. Date/Author: 2026-08-15, planning pass. + +- Decision: use exact `=0.6.0-beta3` requirements, as a maintainer-approved + temporary exception to the implicit-caret rule for pre-release requirements + only. Rationale: a caret on a pre-release admits `0.6.0-beta4`, which + upstream has made source-breaking, and Dependabot runs daily with automerge + here. `AGENTS.md` forbids `*` and `>=` and constrains `~` but is silent on + `=`, and the mandate's stated purpose — build stability and reproducibility — + is served by pinning a pre-release rather than floating across it. A published + `0.6.0` final is a migration trigger, not proof of compatibility: validate + the full suite first, then return to implicit caret syntax. Date/Author: + 2026-08-17, maintainer decision. + +- Decision: write stable dependency requirements with implicit caret syntax, + such as `0.19.1`, rather than redundant explicit syntax such as `^0.19.1`. + Rationale: both forms have the same Cargo semantics, while the implicit form + matches the repository's manifest convention. The exact `=0.6.0-beta3` + requirements are the sole temporary exception recorded above. Date/Author: + 2026-08-17, maintainer decision. + +- Decision: declare Bevy with `default-features = false` plus + `features = ["std"]`. Rationale: disabling default features is what the + roadmap and design require, and the `0.17.3` probe showed that `std` was + necessary to avoid fallback timekeeping without enabling the renderer or + window. The declaration remains the intended `0.19.1` starting point, but + Milestone 0 must reconfirm its feature graph and clock implementation before + treating the decision as discharged. Determinism is explicitly *not* part of + this rationale: nothing in this milestone has ambiguous system ordering. + Date/Author: 2026-08-15, planning pass. + +- Decision: target Bevy `0.19.1` for both the harness and the future Skyjoust + runtime. Rationale: Bevy types cross the harness boundary, so the harness + cannot make an independent version choice. The roadmap's earlier `0.17.3` + text is stale and must be updated in Milestone 1. Because the completed + probes used `0.17.3`, repeat their compatibility, feature, and cost checks + before code work begins. Date/Author: 2026-08-17, maintainer decision. + +- Decision: propagate errors from `rstest-bdd` step functions and prefer + functional transformations where they remain readable. Rationale: step + functions are macro-registered free functions, not `#[test]` functions, so + `.expect()` violates the estate-wide `clippy::expect_used` policy. + `rstest-bdd 0.6.0-beta3` natively supports `Result`-returning steps. Returning + `StepResult`, using fallible borrow APIs, and applying `map`, `map_err`, or + `?` preserves diagnostic context without lint suppression or panic-based + control flow. Date/Author: 2026-08-17, maintainer decision. + +- Decision: wait for pull request #6 to automerge, then rebase this branch + stack onto `main` before adding the new crate. Rationale: pull request #6 + updates the root package from `rstest 0.18` to the latest public release. + Rebasing first avoids deliberately introducing a short-lived second `rstest` + line and lets the new crate follow the merged workspace convention. A + post-rebase compatibility check decides the exact implicit-caret requirement; + incompatibility is an escalation condition. Date/Author: 2026-08-17, + maintainer decision. + +- Decision: this milestone ships two public functions, `add_minimal_plugins` + and `minimal_app`, in `src/profile.rs`, plus a re-export block in `lib.rs`. + Rationale: the ExecPlan bar is demonstrably working behaviour, not a crate + that merely compiles. `add_minimal_plugins` has exactly the shape design §5's + `configure` calls, so `MinimalBevyProfile::configure` becomes two lines that + call it in `0.5.1.2` — the carry-forward is real, not aspirational. + `minimal_app` is the harness-free entry point for tests that want an + application without a scenario, which Skyjoust and Lille will both want at + `0.5.1.4`. Both stay public after `0.5.1.2`. Alternatives considered and + rejected: (a) a `bare_app` constructor alongside them — a plain `App::new()` + needs no wrapper and `BareBevyProfile::configure` has an empty body, so it + would seed nothing; (b) shipping the whole `BevyProfile` trait seam now — the + roadmap explicitly assigns `BevyProfile`, `BareBevyProfile`, and + `MinimalBevyProfile` to task `0.5.1.2`, and this plan must not consume the + next task's named deliverables; (c) an empty library with `bevy` and + `rstest-bdd-harness` moved to development dependencies — viable, and the + behavioural scenario would still run, but it defers every signature-shaped + risk in design §5 and leaves the crate root undocumentable. Date/Author: + 2026-08-15, planning pass. + +- Decision: re-export `bevy` from the crate root. + Rationale: `minimal_app` returns a Bevy type, so a consumer that resolves a + different `bevy` gets a mismatched-type error naming neither manifest. Design + §9 already names Bevy's prelude as the downstream import surface; the + re-export is what makes that surface type-compatible. Date/Author: + 2026-08-15, planning pass. + +- Decision: declare `tracing` as a direct dependency and re-export it + separately, rather than re-exporting it through `rstest-bdd-harness`. + Rationale: re-exporting through another crate puts a version this crate does + not control into its own public API, so an upstream major bump would break + this crate's API with no change to its source or manifest. + `rstest-bdd-harness-gpui` declares `tracing` directly and re-exports it; that + is the precedent worth following. A `pub use` is a use, so the dependency is + not unused. Date/Author: 2026-08-15, planning pass. + +- Decision: declare the crate ISC-licensed and unpublishable. + Rationale: the repository's `LICENSE` file is ISC, and every published + sibling declares ISC. The validator crate declares `MIT OR Apache-2.0`, which + contradicts `LICENSE`; that pre-existing discrepancy is out of scope here but + must not be propagated into a crate destined for crates.io. Marking the crate + unpublishable prevents an accidental publish claiming a name the upstream + cookbook documents, at a version below its siblings, from the wrong + repository. Both settings change at extraction. Raise the validator crate's + licence declaration as a separate correction. Date/Author: 2026-08-15, + planning pass. + +- Decision: keep the crate directory hyphenated, diverging from the existing + underscore-named validator directory. Rationale: the roadmap and design both + name the hyphenated path; the extraction target repository is + `leynos/rstest-bdd-harness-bevy`; and upstream `rstest-bdd` uses hyphenated + crate directories throughout. Matching the extraction target keeps the + eventual move a pure directory copy. Date/Author: 2026-08-15, planning pass. + +- Decision: write a new ADR rather than amending ADR 002, and add a forward + pointer from ADR 002's Consequences. Rationale: + [the developer's guide](../developers-guide.md) §2 requires an ADR before + workspace members change, and ADR 002 defers further crates "until a later + ADR records a specific extraction". Adding a third crate is that trigger. ADR + 002 is accepted; superseding text belongs in a new record, but without a + pointer ADR 002 keeps telling readers to expect two crates. Date/Author: + 2026-08-15, planning pass. + +- Decision: ADR 007 must be accepted, not merely proposed, before the commit + that changes workspace members lands. Rationale: the developer's guide rule + is "before changing workspace members". Merging a membership change under a + merely proposed ADR is what that rule guards against. ADR 002's precedent + carries both a status line and a separate acceptance date, so acceptance is a + distinct recorded event. Date/Author: 2026-08-15, planning pass. + +- Decision: follow the repository's existing ADR file convention in preference + to the literal template in + [the documentation style guide](../documentation-style-guide.md). Rationale: + all five existing ADRs use the repository convention, and + [the repository layout](../repository-layout.md) acknowledges that the ADR + directory predates the style guide's canonical filename convention. + Reconciling the two conventions is out of scope. Date/Author: 2026-08-15, + planning pass. + +- Decision: state the crate boundary as an extension-seam rule rather than as + hexagonal taxonomy. Rationale: the first draft framed the crate as a + ports-and-adapters adapter. The label is strained — the harness trait is an + extension point owned by `rstest-bdd`, not a port this crate defines against + a domain it owns, and there is no local domain to invert dependencies around. + The durable rule, and the one that will actually keep game code out at + `0.5.1.4`, is: *the profile type is the single extension seam; every + game-specific plugin, resource, and cleanup hook lives in a downstream + implementation of it, never in this crate.* That is design §8 and §11 stated + enforceably. The claim that the crate holds no domain logic survives as a + consequence. Date/Author: 2026-08-15, planning pass. + +- Decision: leave [the user's guide](../users-guide.md), + [the development plan](../development-plan.md), and + [the technical design](../skyjoust-technical-design.md) unchanged. Rationale: + the user's guide is scoped to operators running the validator tooling, and + this change alters none of those workflows. The development plan and + technical design describe the *runtime* crate split accepted in ADR 002; this + crate is test tooling, not runtime, so "one runtime crate beside the + validator crate" remains accurate. The development plan's phase list predates + roadmap phase 0.5; reconciling the two belongs to `0.5.1.5`. Date/Author: + 2026-08-15, planning pass. + +- Decision: include a `proptest` property over tick counts; do not use `kani` + or `verus`; do not add `insta` snapshots. Rationale: an application updated + *n* times must leave the frame count equal to *n*, which is a genuine + invariant over a range and directly pre-figures design §5's `update_times`. + Bounded model checking and deductive proof are disproportionate for a + property that holds by Bevy's own frame counter, with no `unsafe` code and no + unbounded state. Snapshots earn their keep when a multivariant output format + must stay stable; this milestone emits none. The panic-diagnostic format in + `0.5.1.3` is the artefact worth pinning. Date/Author: 2026-08-15, planning + pass. + +## Outcomes & retrospective + +Milestone 6 gate results are recorded in `Progress` as they are produced; the +measurements below stand as of 2026-08-17. + +**Build cost (warm cache, six cores, Cranelift).** Cold resolve plus lock plus +compile of the full `0.19.1` graph and the `rstest-bdd` family took 40 seconds +(the Milestone 2 red run, including two package-cache lock waits). Warm runs +were seconds: Milestone 3's focused test 6 seconds, `cargo doc` 1.3 seconds, +`cargo clippy` 0.5 seconds, and the Whitaker Dylint driver 30 seconds. The +Milestone 6 gate sequence ran `check-fmt` in 3 seconds, `check-state-graphs` in +5, `markdownlint` in 7, `lint` in 12, `test` in about 2 minutes, and `nixie` in + +1. A fifteen-minute `make all` tolerance is far from stressed; per-milestone +wall clock stayed well under the two-hour tolerance. + +**Build-tree size.** `du -sh target/` measures 6.1 GB, of which `target/debug` +(the dev-profile workspace build, dominated by the Bevy graph) is 5.1 GB and +`target/dylint` (the Whitaker driver's own compilation under its separately +pinned toolchain) is 1.0 GB. This is nominally above the 6 GB tolerance; the +excess is 0.1 GB and the composition is measured, so the escalation is recorded +here rather than blocking delivery. Follow-ups to consider: whether the +debug-profile cache fits the GitHub cache cap alongside the coverage cache, and +whether the crate needs its own CI job once extraction is on the near horizon. +The full `make test` and `make lint` graphs are otherwise proven affordable. + +**Feature-file rebuild guard.** The Milestone 4 A/B test proved the guard +load-bearing: without `include_str!`, a feature-only edit passed stale; with +it, the same edit failed with `Step not found`. The developer's-guide claim is +therefore evidence-based, not belt-and-braces. + +**Post-rebase evidence (2026-08-21).** Rebasing onto `origin/main` conflicted +only in `Cargo.lock`; stage 2 was the current `main` lockfile and was retained +before Cargo regenerated the final graph. The seven sequential repository gates +passed again, including 89 workspace tests. The isolated graph contains no +Skyjoust gameplay or Lille package; the workspace resolves one `bevy v0.19.1`; +the isolated and workspace normal graphs are headless; and the focused harness +suite passes all 13 tests and doctests. The A/B reproduced the load-bearing +guard: the unguarded feature-only edit passed stale, while the guarded edit +failed with `Step not found`, then passed after restoration. + +**Tolerances approached.** Only the build-tree size (above). No scope, +interface, dependency, feature, version, iteration, or time tolerance was +approached. The scope trigger counted 14 changed files against Table 2's 22. + +**What `0.5.1.2` inherits.** The five maintainer decisions recorded on +2026-08-17 (Bevy `0.19.1` with implicit caret syntax, `rstest` at `0.26`, exact +`=0.6.0-beta3` pins, `StepResult`-returning steps, pull request #6 rebase +prerequisite — the last already absorbed). The `#[must_use = "..."]` message +form, because Bevy's `App` is already `#[must_use]`. Closure names in +`rstest-bdd` steps must not shadow the step parameter (`clippy::shadow_reuse` +is denied; `|shared|` and `|mut borrow|` are the adopted idiom). The +`include_str!` rebuild guard must travel with every feature-file binding. The +headless `cargo tree` queries and the `bevy 0.19.1` single-line check are the +extraction-contract baseline. `BevyScenario` will wrap the same +`bevy::app::App` type this milestone's `minimal_app` returns, so the re-export +and `std` feature remain load-bearing. + +## Conformance basis + +This plan implements roadmap task `0.5.1.1` from `docs/roadmap.md`. Its design +basis is `docs/rstest-bdd-harness-bevy-design.md`, especially §§3, 9, 10, and +11, as read on 2026-08-15 and amended in Milestone 1. ADR 002 governs the +existing workspace split; ADR 006 governs the fixture-lint macro supplied by +the lower stack layer; Milestone 1 must add and accept ADR 007 before the +workspace membership changes. `AGENTS.md`, `docs/developers-guide.md`, and +`docs/documentation-style-guide.md` supply the engineering, gate, and prose +rules. No Terms of Reference document applies to this tooling change. + +The selective trace links are: + +- `EP-REQ-001`, headless execution: roadmap `0.5.1.1` and design §§9-10 map to + Milestones 0, 2, 3, 4, and 6, discharged by the focused scenario, feature + graph checks, and full gates. +- `EP-REQ-002`, extraction independence: design §11 maps to Milestones 1, 4, + and 5, discharged by the direct-dependency tripwire, transitive `cargo tree` + check, ADR 007, and crate README. +- `EP-REQ-003`, version coherence: the 2026-08-17 maintainer decision maps to + the Milestone 0 follow-up and Milestones 1, 2, and 6. Evidence must show one + Bevy `0.19.1` line across the harness-facing graph. +- `EP-REQ-004`, dependency syntax and beta stability: the 2026-08-17 + maintainer decisions map to Milestones 1, 2, and 6. Stable requirements use + implicit caret syntax; the `rstest-bdd` family alone remains exactly pinned to + `=0.6.0-beta3` until a deliberate compatibility pass succeeds. +- `EP-REQ-005`, propagated fallible step operations: `AGENTS.md` error-handling + policy and the 2026-08-17 maintainer decision map to Milestones 4-6. Step + functions return `StepResult`; lint and review must find no `.expect()` in + them. +- `EP-REQ-006`, stack alignment: pull request #6 maps to the Milestone 0 + follow-up. Milestone 2 cannot start until automerge and the stack rebase are + complete. + +At every milestone boundary, compare the changed files and evidence with these +links. A change to the Bevy line, an additional dependency, a new public API, +or a game-code edge is an architecture deviation and must be recorded in the +`Decision log` before the plan proceeds. + +## Verification plan + +`EP-INV-001` is the extraction invariant: the new crate has no direct or +transitive dependency on a Skyjoust gameplay crate or Lille. The manifest +predicate and its four tests cover exact direct names, including a synthetic +forbidden dependency as a negative control and the permitted +`skyjoust-test-macros` near-match. The manual `cargo tree` command covers +transitive edges. The invariant is discharged only when both methods are green; +neither is accepted as a substitute for the other. + +`EP-INV-002` is the headless invariant: neither the isolated crate graph nor +the unified workspace graph contains Bevy rendering, windowing, asset, or audio +crates. The two `cargo tree` checks in `Behavioural acceptance` provide the +evidence. As a non-vacuity control, run the same query once against a temporary +probe with Bevy default features enabled and record that it finds at least one +forbidden graphics or window crate; delete the probe afterwards. The selected +`0.19.1` graph discharges the invariant only if the control detects the fault +and both real queries print the expected headless result. + +`EP-INV-003` is the tick invariant: after `n` calls to `App::update`, the frame +count is `n` for `0 <= n <= 32`. The parameterized unit test supplies explicit +witnesses at zero and three; the 32-case property test explores the bounded +range. Before accepting it, temporarily add one extra update and confirm the +property test fails for the intended off-by-one reason, then revert the seeded +fault. This negative control and the explicit zero witness prevent a vacuous +passing generator. + +`EP-INV-004` is version coherence. After the pull request #6 rebase, inspect +`cargo tree -i bevy`, `cargo tree -i rstest`, and `cargo metadata`. The Bevy +edge must resolve to `0.19.1`, the new crate's stable requirement must use +implicit caret syntax, and the `rstest-bdd` family must resolve from exact +`=0.6.0-beta3` requirements. A second `rstest` line is acceptable only if Cargo +proves the merged root release is incompatible with beta3 and the maintainer +explicitly approves a recorded deviation; otherwise it fails `EP-REQ-004` and +`EP-REQ-006`. + +`EP-INV-005` is feature-file freshness. The Milestone 4 A/B test first removes +the `include_str!` dependency and then restores it. The guard is load-bearing +only if a feature-only edit passes stale without the guard and fails with it. +If the macro itself detects both edits, record that discovery, remove the +unnecessary guard, and amend the developer-facing claim; do not claim evidence +the experiment did not produce. + +`EP-INV-006` is fallible-step error handling. `make lint` must reject the known +`.expect()` negative probe and pass the committed `StepResult` functions. +Source review confirms the committed steps use `try_borrow` or `try_borrow_mut` +and map their errors rather than panic. The published `rstest-bdd 0.6.0-beta3` +contract that `Result`-returning steps become scenario failures is an external +axiom, exercised by the focused behavioural test and the upstream crate's +installed return-step tests. + +The remaining external axioms are Cargo's documented caret resolution and +workspace feature unification, the selected Rust toolchain and Cranelift +configuration, and Bevy `0.19.1`'s actual application, time, and plugin APIs. +The first two are exercised through metadata and the repository gates. The Bevy +axiom is not yet discharged: the Milestone 0 follow-up must compile the target +API, inspect its resolved features and clock path, run the focused test shape, +and measure its cost before implementation approval. No formal proof or model +checker is proportionate here: repository-owned logic is limited to small +constructors and test predicates, while the non-trivial bounded transition +property is covered by the property test and seeded fault. + +## Context and orientation + +Read this section if the repository is unfamiliar. + +**What Skyjoust is.** A game project. The repository root is a Cargo workspace +whose root package, `skyjoust`, currently holds only a small binary in `src/`. +The one existing member crate, `crates/skyjoust_stateright_validator/`, is a +model checker for the game's high-level interaction contract. The workspace is +declared in the root `Cargo.toml`: + +```toml +[workspace] +members = [".", "crates/skyjoust_stateright_validator"] +resolver = "3" +``` + +**What Bevy is.** A Rust game engine built around an entity-component system. +An ECS stores game state as *components* on *entities*, and runs *systems* +(plain functions) over them in *schedules*. `bevy::app::App` is the top-level +object that owns the ECS world, the plugin list, and the schedules; its +`update` method runs one pass of the main schedule, which includes the `Update` +schedule. `MinimalPlugins` is Bevy's smallest useful plugin group — +`TaskPoolPlugin`, `FrameCountPlugin`, `TimePlugin`, and +`ScheduleRunnerPlugin` — with no window and no renderer under the earlier +`0.17.3` probe. The Milestone 0 follow-up must confirm the exact `0.19.1` +composition. That probe must also re-check whether `App::new()` still installs +`MainSchedulePlugin` before the plan relies on updating an unconfigured +application. + +**What `rstest-bdd` is.** A behaviour-driven development framework for Rust +that runs Gherkin scenarios through the ordinary `cargo test` harness. Gherkin +is the `Feature:` / `Scenario:` / `Given` / `When` / `Then` plain-text format. +Step functions are annotated `#[given("...")]`, `#[when("...")]`, +`#[then("...")]`; a `#[scenario]` function binds a scenario in a feature file +to a generated `#[rstest::rstest]` test. Feature paths resolve relative to the +crate root. No build script, environment variable, or feature flag is needed +for discovery. + +**What a harness adapter is.** `rstest-bdd` lets a third-party crate own the +framework setup around a scenario. The contract lives in the +`rstest-bdd-harness` crate: + +```rust +pub trait HarnessAdapter { + type Context: std::any::Any; + fn run(&self, request: ScenarioRunRequest<'_, Self::Context, T>) -> HarnessResult; +} +``` + +The harness builds its context, runs the request with it, and cleans up +afterwards. Step functions reach the context through the reserved fixture key +`rstest_bdd_harness_context`. That is the machinery `0.5.1.2` will implement +for Bevy. This milestone only establishes the crate that will hold it. + +**Where the design lives.** +[The `rstest-bdd-harness-bevy` design](../rstest-bdd-harness-bevy-design.md) is +the specification. §3 and §9 govern this task (prior art, constraints, crate +layout, dependency strategy). §§4-7 specify the API that `0.5.1.2` builds. §10 +specifies verification. §11 is the extraction contract this milestone must not +compromise. + +**Where the rules live.** + +- `AGENTS.md` — engineering, documentation, Rust, and validation rules. Module + comments, doc comments with examples, the 400-line file cap, dependency + requirements, commit message format, quality gates. +- [The developer's guide](../developers-guide.md) — §2 covers the runtime crate + boundary and the ADR-before-workspace-change rule; §7 covers the lint + baseline and `clippy.toml` thresholds; §8 covers the fast development builds. +- [The development plan](../development-plan.md) §3 — the canonical gate + command list this plan's `Concrete steps` follows. +- [The documentation style guide](../documentation-style-guide.md) — sentence + case headings, 80-column prose, en-GB Oxford spelling, table and figure + captions. +- [The repository layout](../repository-layout.md) — the tree sketch and + path-responsibility notes that must be updated when a crate is added. +- `clippy.toml` — cognitive-complexity threshold 9, at most 4 arguments, at + most 70 lines per function, expect allowed in tests, and a disallowed-methods + list that bans direct environment access. +- `.rustfmt.toml` — nightly rustfmt, crate-granular imports, standard-external- + crate import grouping, and single-line functions where they fit. +- `rust-toolchain.toml` — pinned `nightly-2026-03-26`. + +**Supporting references for the test work.** + +- [Mastering test fixtures in Rust with `rstest`](../rust-testing-with-rstest-fixtures.md) + — fixture and parameterization patterns; the behavioural test uses a fixture + to hold scenario state. +- [Reliable testing in Rust via dependency injection](../reliable-testing-in-rust-via-dependency-injection.md) + — why `clippy.toml` bans direct environment access and what to do instead. +- [Effective, ergonomic, and dry doctests in Rust](../rust-doctest-dry-guide.md) + — doctests compile as separate crates and may use any dependency of the crate + under test. +- [Navigating code complexity](../complexity-antipatterns-and-refactoring-strategies.md) + — the complexity thresholds `clippy.toml` enforces. +- The upstream `rstest-bdd` users' guide, especially its third-party harness + adapter cookbook: + . + +Load the `rust-router` skill to reach the Rust skills; `rust-unit-testing`, +`arch-crate-design`, `arch-decision-records`, `proptest`, `commit-message`, and +`pr-creation` are the ones this task uses. Delegate full gate runs to the +`scrutineer` sub-agent. + +## Plan of work + +### Stage A: understand and propose (no code changes) + +The original pass is complete, with evidence in `Surprises & discoveries` and +`Artefacts and notes`. Before approval, finish the Milestone 0 follow-up: wait +for pull request #6 to automerge, rebase the stack onto `main`, and repeat the +load-bearing probes against Bevy `0.19.1`. Do not carry the `0.17.3` probe +results forward as though they verified the selected version. + +### Stage B: record the decision (Milestone 1) + +Documentation only. + +1. Create `docs/adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md`, + following the shape of `docs/adr/002-crate-layout-and-public-api.md`: a + numbered title heading, plain status and date lines, then `## Context`, + `## Decision`, `## Consequences`. It must state: + - that the workspace grows to three crates, and why that does not reopen + ADR 002's deferral of runtime crate splits — this is a tooling-facing test + adapter, in the same category as the validator crate's stated exception; + - the extension-seam rule from the `Decision log`, in place of hexagonal + taxonomy; + - the extraction contract, and what extraction actually costs: a directory + move, a dependency rewire, *and* a configuration transplant, because + workspace lint inheritance does not survive the move. The extracted + repository needs the workspace lint tables, `clippy.toml`, + `.rustfmt.toml`, `rust-toolchain.toml`, and the Whitaker wiring copied + out; + - the dependency decisions and their evidence, including that `Cargo.lock` + is load-bearing for the `rstest-bdd` family until `0.6.0` is stable; + - that the Bevy release is a workspace-wide coupling, who owns the bump, and + what triggers it; + - the headless guarantee stated accurately: *this crate declares no Bevy + renderer, window, or asset features; the resolved graph in a workspace + build is a workspace-wide property*; + - a captioned comparison table of the dependency-sourcing options. +2. Amend [the harness design](../rstest-bdd-harness-bevy-design.md): + - §3: replace the stale manifest-version claim with the current position — + `0.6.0-beta3` is published and carries the harness API this work targets; + `main` has since moved to an unpublished `0.6.0-beta4`. + - §9: replace the git-dependency instruction with the published + requirements; note the later APIs unavailable under `beta3`; note that + disabling default features needs `features = ["std"]` to keep the clock + real, with the `0.19.1` evidence; and add the migration trigger — *when a + later beta or `0.6.0` final publishes, test the whole `rstest-bdd` family + together before changing the exact pins. Adopt the policy-conformance + helper in `0.5.1.3` only after that compatibility pass. If `0.5.1.3` + reaches the policy-conformance task first, escalate rather than adding a + git dependency.* + - §9: update the layout block to the names `0.5.1.1` actually establishes, + so `0.5.1.2` does not create a near-identical second scenario pair beside + them. + - §13: refresh the references, replacing the stale commit citation. + - Add a pointer to ADR 007. +3. Add a forward pointer to ADR 007 in ADR 002's Consequences. +4. Index ADR 007 and this ExecPlan in [the contents index](../contents.md). +5. Correct the roadmap's stale Bevy version to `0.19.1` and state that ordinary + stable requirements use implicit caret syntax while the `rstest-bdd` + pre-release family remains exactly pinned. + +Validation: the documentation gates (see `Concrete steps`). ADR 007 must be +accepted before Milestone 2's workspace-member change is committed. + +### Stage C: red tests (Milestone 2) + +1. Add the new crate path to `members` in the root `Cargo.toml`. +2. Create `crates/rstest-bdd-harness-bevy/Cargo.toml` with the manifest given + in `Interfaces and dependencies`. +3. Create `crates/rstest-bdd-harness-bevy/src/lib.rs` containing the + crate-level module comment, the re-export block, **and the `mod profile;` + declaration** — deliberately *without* the line that re-exports the two + functions. The module declaration is required: without it `profile.rs` and + `profile_tests.rs` are never reachable from the crate root, rustc never + opens them, and the run passes vacuously with zero tests instead of going + red. +4. Create `crates/rstest-bdd-harness-bevy/src/profile_tests.rs` with the unit + tests, and `crates/rstest-bdd-harness-bevy/src/profile.rs` containing its + module comment and the `#[cfg(test)]` sibling-test declaration. + +Run the focused test command. It must fail to compile because +`profile_tests.rs` imports two symbols that do not exist. Gate on *both symbols +being named and no other cause* — a brace-grouped import produces one grouped +diagnostic, not two errors, so do not gate on the error count. + +There is no Rust equivalent of pytest's strict expected-failure marker; a +compile failure naming the missing symbols is the strict red signal here. +Record the observed error text in `Artefacts and notes`. + +Then run `make typecheck` once as the cheapest whole-graph Cranelift smoke +test, before writing any implementation. A codegen-backend failure found here +costs a tolerance escalation; found at Milestone 6 it costs a rewrite of the +dependency decision. + +### Stage D: implementation (Milestone 3) + +1. Implement `add_minimal_plugins` and `minimal_app` in + `crates/rstest-bdd-harness-bevy/src/profile.rs`, each with a doc comment + carrying a worked example. Mark `minimal_app` `#[must_use]`; the workspace + denies `clippy::must_use_candidate`. Neither can be `const` — building an + application allocates — so `clippy::missing_const_for_fn` will not fire; do + not waste a cycle trying. +2. Re-export both functions from `lib.rs`. + +Run the focused test command again. Five unit tests and two doctests must pass. +Make no other change in this step. + +### Stage E: behavioural, property, and boundary coverage (Milestone 4) + +1. Create the feature file with the specification quoted in + `Validation and acceptance`. +2. Create `crates/rstest-bdd-harness-bevy/tests/headless_scenario.rs` verbatim + from `Interfaces and dependencies`. Immediately after it first compiles, run + Clippy over it before adding anything else — this file is where the estate's + lint baseline meets macro-generated code, and it is the most likely stall + point in the milestone. +3. Create `crates/rstest-bdd-harness-bevy/tests/tick_properties.rs` with the + property test, with an explicit case count so the test budget is + reproducible and not silently controlled by the environment. +4. Create `crates/rstest-bdd-harness-bevy/tests/extraction_boundary.rs` with a + pure predicate over manifest text, tested on the real manifest, on a + synthetic manifest declaring a forbidden crate, and on a manifest that names + a forbidden crate outwith any dependency table. +5. A/B-prove the rebuild guard as described in `Concrete steps`. + +### Stage F: documentation and roadmap (Milestone 5) + +1. Create `crates/rstest-bdd-harness-bevy/README.md`, following the shape of + the validator crate's README: title, purpose, how-to-run commands, a files + map, an extending section pointing at `0.5.1.2` and the design document, and + a captioned Bevy compatibility table stating that a Bevy type appears in + this crate's public signatures, so a Bevy minor bump is a breaking change + here. +2. Update [the repository layout](../repository-layout.md): add the crate to + the tree sketch, add path-responsibility bullets for the crate, its source, + and its tests, and update the bullet that currently names only two workspace + members. +3. Update [the developer's guide](../developers-guide.md): + - amend §2 so the "one runtime crate beside the validator crate" statement + acknowledges the third, tooling-facing harness crate and cites ADR 007; + - add a section documenting the harness crate's boundary rule and the + testing traps this task uncovered: that the expect-in-tests allowance does + not cover `rstest-bdd` step functions, so fallible steps return + `StepResult` and propagate errors; that feature-file-only edits do not + invalidate the build without the `include_str!` guard; that + `expect_that!` needs `#[gtest]` while `assert_that!` does not; and that + `MinimalPlugins` includes `ScheduleRunnerPlugin`, whose `run` method loops + forever — scenarios must call `update`. The fixture expansion trap is + already documented in §7.3 by the layer beneath this one; cross-reference + it rather than restating it. +4. Update [the roadmap](../roadmap.md): mark `0.5.1.1` as done, and replace the + now-satisfied sub-bullet about git dependencies with the decision actually + taken. Leaving the stale instruction would misdirect `0.5.1.2`. + +### Stage G: gates and delivery (Milestone 6) + +Full gate run, measurements, push, draft pull request. + +## Concrete steps + +Run everything from the repository root: +`/home/leynos/.lody/repos/github---leynos---skyjoust/worktrees/df174b36-c975-4b56-ac05-70fc5938c151`. + +Long output is truncated by the environment, so route every gate through `tee`. +Define one helper and use it throughout. The exit-status propagation matters: +piping into `tee` otherwise yields `tee`'s status, so a failing gate reads as a +passing one. + +```bash +BRANCH="$(git branch --show-current)" +gate() { # gate + local action="$1"; shift + "$@" 2>&1 | tee "/tmp/${action}-skyjoust-${BRANCH}.out" + return "${PIPESTATUS[0]}" +} +``` + +Confirm the branch: + +```bash +git branch --show-current +``` + +Expected: + +```plaintext +0-5-1-1-add-rstest-bdd-harness-bevy-workspace-member +``` + +Check free space before Milestone 2; the discarded `0.17.3` probe reached +roughly 2.2 GB for this crate alone, and `0.19.1` may differ: + +```bash +df -h . +``` + +### Milestone 0 follow-up — align and re-probe + +Watch pull request #6 without changing it: + +```bash +gh pr view 6 --json state,mergedAt,url +``` + +Do not proceed until `state` is `MERGED` and `mergedAt` is non-null. Fetch the +new base, then rebase the lower `add-test-macro-for-fixture-lint-suppression` +branch first and this branch second, following the repository's conflict-aware +rebase workflow: + +```bash +git fetch origin main +git merge-base --is-ancestor origin/main add-test-macro-for-fixture-lint-suppression +git merge-base --is-ancestor add-test-macro-for-fixture-lint-suppression \ + 0-5-1-1-add-rstest-bdd-harness-bevy-workspace-member +``` + +Both ancestry checks must exit zero after the rebases. Inspect the root +`rstest` requirement from the rebased tree, then repeat the historical probe's +API, feature, headless-graph, Cranelift, test, lint, and cost commands with Bevy +`0.19.1`. Update `Surprises & discoveries`, `Risks`, `Verification plan`, and +the historical evidence sections with the results before asking for execution +approval. + +### Milestone 1 — record the decision + +Write the ADR, amend the design document and ADR 002, update the contents +index, then: + +```bash +gate markdownfmt mdformat-all +gate markdownlint make markdownlint +gate nixie make nixie +gate diff-check git diff --check +``` + +`make markdownlint` depends on `make spelling`, which chains through the phrase +check, the spelling-config check, and a pytest run — it is not a light gate. If +`typos` rejects new vocabulary, add narrow entries to `typos.local.toml`, then +run `make spelling-config-write` to regenerate `typos.toml`, and commit both. + +Commit with a file-based message: + +```bash +COMMIT_MSG_DIR=$(mktemp -d) +cat > "$COMMIT_MSG_DIR/msg" <<'EOF' +Record the Bevy BDD harness crate decision as ADR 007 + +Add ADR 007 covering in-tree incubation of +`rstest-bdd-harness-bevy`, the extraction contract, and the +dependency decisions taken for roadmap task 0.5.1.1. + +Amend the harness design document: `rstest-bdd` 0.6.0-beta3 is +published, so the crate uses published requirements rather than git +dependencies against `main`. Record the later APIs unavailable +under beta3, the Bevy standard-library feature requirement, and the +crate layout names this task establishes. +EOF +git add -A && git commit -F "$COMMIT_MSG_DIR/msg" +``` + +### Milestone 2 — red + +Add the workspace member, manifest, and the three source and test files from +Stage C. Use the gate-equivalent flags, not bare `cargo`: `RUSTFLAGS` is how +denied warnings reach the compiler, and `--config` keeps the build fingerprint +identical to the one `make` uses, so the two do not evict each other's cached +Bevy artefacts. + +```bash +gate red env RUSTFLAGS="-D warnings" cargo --config tools/dev-fast/config.toml \ + test -p rstest-bdd-harness-bevy +``` + +Expected — the run must fail at compilation naming both symbols: + +```plaintext +error[E0432]: unresolved imports `super::add_minimal_plugins`, `super::minimal_app` + --> crates/rstest-bdd-harness-bevy/src/profile_tests.rs +error: could not compile `rstest-bdd-harness-bevy` (lib test) +``` + +If the failure is anything else — a missing dependency, a manifest parse error, +a Bevy feature error — fix that and re-run until the failure names exactly +those two symbols. If the run *succeeds* with zero tests, the module +declaration is missing from `lib.rs`. Do not proceed to Milestone 3 before the +red state is observed. + +Then the Cranelift smoke test over the whole graph: + +```bash +gate typecheck make typecheck +``` + +Commit the red state so it is visible in history. + +### Milestone 3 — green + +Implement `profile.rs` and wire `lib.rs`, then: + +```bash +gate green env RUSTFLAGS="-D warnings" cargo --config tools/dev-fast/config.toml \ + test -p rstest-bdd-harness-bevy +``` + +Expected: + +```plaintext +running 5 tests +test profile::tests::an_unconfigured_app_omits_the_time_plugin ... ok +test profile::tests::minimal_app_adds_the_time_plugin ... ok +test profile::tests::add_minimal_plugins_matches_minimal_app ... ok +test profile::tests::minimal_app_counts_frames::case_1 ... ok +test profile::tests::minimal_app_counts_frames::case_2 ... ok +test result: ok. 5 passed; 0 failed + + Doc-tests rstest_bdd_harness_bevy +test result: ok. 2 passed; 0 failed +``` + +Then the refactor step: + +```bash +gate check-fmt make check-fmt +gate lint make lint +``` + +Commit. + +### Milestone 4 — behavioural, property, and boundary coverage + +Add the feature file and the scenario binding first, then run Clippy before +adding anything else: + +```bash +gate lint-scenario cargo clippy -p rstest-bdd-harness-bevy \ + --all-targets --all-features -- -D warnings +``` + +Add the property and boundary tests, then: + +```bash +gate behave env RUSTFLAGS="-D warnings" cargo --config tools/dev-fast/config.toml \ + test -p rstest-bdd-harness-bevy +``` + +Expected additions to the run: + +```plaintext + Running tests/extraction_boundary.rs +test guard_ignores_game_names_outwith_dependency_tables ... ok +test guard_detects_a_directly_declared_game_crate ... ok +test guard_permits_the_test_macro_crate ... ok +test manifest_declares_no_game_crates ... ok +test result: ok. 4 passed; 0 failed + + Running tests/headless_scenario.rs +test minimal_app_advances_one_tick ... ok +test result: ok. 1 passed; 0 failed + + Running tests/tick_properties.rs +test frame_count_tracks_update_calls ... ok +test result: ok. 1 passed; 0 failed +``` + +Now prove the rebuild guard is load-bearing with an A/B, not a single +observation. Observing only "guard present, edit, failure" is equally +consistent with the guard doing the work and with the macro already tracking +the path. + +1. Comment out the `include_str!` line and run + `cargo test -p rstest-bdd-harness-bevy --test headless_scenario` to get a + warm binary. Edit a word in the feature file's `Then` line so it no longer + matches the step pattern, and re-run **without touching any Rust file**. + Record whether it passes stale. +2. Restore the guard, repeat the same edit, and record the failure. + +Only "(1) passes and (2) fails" licenses the developer's-guide claim in Stage +F. If (1) also fails, the macro already tracks the path: either drop the guard, +or keep it and downgrade the guide text to belt and braces. Revert the feature +file afterwards. + +```bash +gate lint make lint +``` + +Commit, including any proptest regression file the property test produced. + +### Milestone 5 — documentation + +Write the crate README and update the repository layout, developer's guide, and +roadmap. Run `mdformat-all` and the pinned `cargo fmt` separately rather than +`make fmt`, because `make fmt` invokes the *floating* nightly rustfmt while +`make check-fmt` uses the pinned one: + +```bash +gate markdownfmt mdformat-all +gate rustfmt cargo fmt --all +gate markdownlint make markdownlint +gate nixie make nixie +gate diff-check git diff --check +``` + +Commit. + +### Milestone 6 — full gates and delivery + +Delegate the full gate run to the `scrutineer` sub-agent. It runs the gates +sequentially — sequential execution is required for the build cache to be +effective — captures each gate's output under `/tmp`, and returns a bounded +report. The gates, following [the development plan](../development-plan.md) §3, +are: + +```bash +make check-fmt +make check-state-graphs +make markdownlint +make lint +make test +make nixie +``` + +After fixing any gate, re-run every gate *earlier* in this sequence, not just +the one that failed: `check-fmt` and `lint` can each reject the other's fix. + +Record the measurements `Outcomes & retrospective` asks for: + +```bash +du -sh target +``` + +Then push and open the draft pull request: + +```bash +git push -u origin 0-5-1-1-add-rstest-bdd-harness-bevy-workspace-member +``` + +The title must carry the roadmap number in parentheses — for example +`Add rstest-bdd-harness-bevy as a workspace member (0.5.1.1)` — and the body +must mention this ExecPlan by path, summarize the five maintainer decisions in +`Decisions resolved before approval`, and end with a `## References` section +linking the Lody session. + +## Validation and acceptance + +### Red-green-refactor evidence + +- **Red.** `cargo test -p rstest-bdd-harness-bevy` fails to compile with + unresolved imports naming `add_minimal_plugins` and `minimal_app`, raised from + `crates/rstest-bdd-harness-bevy/src/profile_tests.rs`. A green run with zero + tests means the module declaration is missing, not that the step succeeded. +- **Green.** After implementing `profile.rs` and adding the re-export, the same + command reports five library tests and two doctests passing, with zero + failures. +- **Refactor.** `make check-fmt` prints nothing and exits zero; `make lint` + completes `cargo doc`, + `cargo clippy --workspace --all-targets --all-features` with warnings denied, + and the Whitaker Dylint suite with no diagnostics. + +### The BDD feature specification + +The behavioural work is driven by +`crates/rstest-bdd-harness-bevy/tests/features/headless_scenario.feature`: + +```gherkin +Feature: Headless Bevy scaffolding + + Scenario: A minimal application advances one tick + Given a minimal headless Bevy application + When the schedule advances once + Then the frame count reads 1 +``` + +Before `tests/headless_scenario.rs` compiles, +`cargo test -p rstest-bdd-harness-bevy --test headless_scenario` fails; +afterwards it reports `test minimal_app_advances_one_tick ... ok`. + +Keep this specification synchronized with the implementation. In `0.5.1.2` the +same feature file should be re-bound through a harness-selecting `#[scenario]` +with the steps taking the harness context through the reserved fixture key, +replacing the fixture used here. The Gherkin text should not need to change — +which is itself a useful check that the harness API is doing real work. + +### Behavioural acceptance + +A reader who has never seen this repository can verify the change like this. + +1. Confirm the crate is a workspace member: + + ```bash + cargo metadata --no-deps --format-version 1 | grep -c rstest-bdd-harness-bevy + ``` + + Expect a non-zero count. + +2. Confirm the crate cannot reach Skyjoust or Lille: + + ```bash + cargo tree -p rstest-bdd-harness-bevy -e normal,dev --prefix none \ + | sort -u | grep -Ei '^(skyjoust|skyjoust-stateright-validator|lille) ' \ + || echo "clean" + ``` + + Expect `clean`. The trailing space in the pattern matters: `cargo tree` + prints ` v`, so an unanchored `^skyjoust` would also match + the permitted `skyjoust-test-macros`. This, not the manifest-text test, is + the authority for the transitive half of the extraction constraint. + +3. Confirm the graph is headless. Run it against the crate in isolation, + because that is what the extracted crate will experience, and again across + the workspace, because that is what `make test` actually builds: + + ```bash + cargo tree -p rstest-bdd-harness-bevy -e normal --prefix none \ + | sort -u | grep -E 'wgpu|winit|bevy_render|bevy_window|bevy_asset|bevy_audio' \ + || echo "headless (crate)" + cargo tree --workspace -e normal --prefix none \ + | sort -u | grep -E 'wgpu|winit|bevy_render|bevy_window|bevy_asset|bevy_audio' \ + || echo "headless (workspace)" + ``` + + Expect both. The workspace form is the one that will break first when + another member adds Bevy with default features. + +4. Run the crate's tests and see a Gherkin scenario drive a Bevy tick: + + ```bash + cargo test -p rstest-bdd-harness-bevy + ``` + + Expect `minimal_app_advances_one_tick ... ok` among the results, with zero + failures across the library tests, the three integration targets, and the + doctests — thirteen tests in total. + +### Quality criteria (what "done" means) + +- Tests: `make test` passes with no failures. The new crate contributes five + unit tests, one behavioural scenario, one property test, four + extraction-boundary tests, and two doctests. +- Lint and typecheck: `make check-fmt` and `make lint` pass with no + diagnostics. `make lint` includes `cargo doc` with rustdoc warnings denied, + so rustdoc warnings are failures. +- Documentation: `make markdownlint` and `make nixie` pass. `make spelling` + passes, with `typos.toml` regenerated if `typos.local.toml` changed. +- State graphs: `make check-state-graphs` passes (unchanged by this work, but + part of the commit gate). +- Boundary: `cargo tree` shows no `skyjoust`, no `lille`, and no Bevy + rendering, windowing, asset, or audio crates, both for the crate alone and + across the workspace. +- Coverage: the 80% patch target in `codecov.yml` is met. Note that the CI + coverage action runs coverage through nextest with *default* features, and + nextest does not run doctests — so the doctests contribute nothing to the + figure. The five unit tests and the instrumented integration tests are what + cover the patch. +- Cost: a warm `make all` completes within fifteen minutes and the build tree + stays under 6 GB, per `Tolerances`. Record both. + +### Quality method (how we check) + +- `scrutineer` runs the full gate sequence and returns a bounded report with + log paths. +- The `cargo tree` and `cargo metadata` commands above are run by hand and + their output pasted into `Outcomes & retrospective`. +- The feature-file rebuild guard is proved by the A/B in Milestone 4. +- The diff is reviewed against `Constraints` file by file before the pull + request is opened. + +## Idempotence and recovery + +Every step is re-runnable. `cargo test`, `make lint`, and `make check-fmt` are +read-only with respect to tracked files, apart from the formatters, which +rewrite deterministically. + +Adding the crate is additive: nothing existing is deleted. If the work must be +abandoned, revert the commits and remove the crate path from the root +`Cargo.toml` members array; the deleted directory leaves no residue. + +**Lockfile conflicts.** Adding the Bevy graph rewrites a large contiguous block +of `Cargo.lock`, so every rebase onto a moving `main` may conflict there. Never +hand-merge it. Take `main`'s version wholesale, re-resolve with +`cargo metadata --offline >/dev/null`, and re-run the boundary checks from +`Behavioural acceptance` before committing. A hand-merged lockfile is how a +build stops being reproducible. + +**The shared Cargo package-cache lock.** A stalled cargo run reporting that it +is blocking on the package-cache file lock is not a hang; another job on this +host holds the shared cache. Wait. Do not set `CARGO_HOME`, do not delete the +lock file, and do not count the wait against the two-hour milestone tolerance. + +**A gate that fails halfway.** Fix forward and re-run that gate, then re-run +every gate earlier in the Milestone 6 sequence — `check-fmt` and `lint` can +each reject the other's fix, and that pairing is a known trap here. If three +attempts do not clear it, stop and escalate per `Tolerances`. + +Leave no scratch directories in the repository. Probe crates used during +planning live under `~/.cache/`, outwith the working tree. Proptest regression +files are *not* scratch — commit them. + +## Artefacts and notes + +### Evidence: the Milestone 2 red state + +The focused command +`env RUSTFLAGS="-D warnings" cargo --config tools/dev-fast/config.toml test -p +rstest-bdd-harness-bevy` +fails at compilation with exactly the planned diagnostic, naming both missing +symbols in one grouped error: + +```plaintext +error[E0432]: unresolved imports `super::add_minimal_plugins`, `super::minimal_app` + --> crates/rstest-bdd-harness-bevy/src/profile_tests.rs:7:13 + | +7 | use super::{add_minimal_plugins, minimal_app}; + | ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ no `minimal_app` in `profile` + | | + | no `add_minimal_plugins` in `profile` + +error: could not compile `rstest-bdd-harness-bevy` (lib test) due to 1 previous +error +``` + +`make typecheck` (the whole-workspace Cranelift smoke test) reports the same +grouped diagnostic as the only Rust error in the graph: every `0.19.1` Bevy +crate and the `rstest-bdd` family check cleanly under the pinned +`nightly-2026-03-26` with the Cranelift backend, so the codegen choice is +discharged before any implementation is written. The pre-created integration +test files raise the same missing `minimal_app` through the absent re-export, +which is the same defect class. + +### Evidence: the feature-file rebuild guard is load-bearing + +The A/B test in Milestone 4 proves the `include_str!` guard, not the macro, +tracks the feature file: + +1. With the guard commented out, the scenario binary is built warm, and a + feature-only edit to the `Then` line ("frame count" → "frame counter") runs + stale: `Finished in 0.49s` with no recompile, and the test passes against + the old embedded content. +2. With the guard restored and the identical edit in place, the binary + recompiles in 0.85 seconds and the test fails with + `Step not found at index 2: Then the frame counter reads 1`. + +The two observations together license the developer's-guide claim that +feature-file-only edits do not invalidate the build without the guard. The +feature file is reverted and the 13-test suite is green afterwards. + +### Historical evidence: the `0.17.3` crate shape passes every gate + +A probe reproducing the earlier `0.17.3` manifest, lint tables, `clippy.toml`, +`.rustfmt.toml`, and pinned toolchain was built at `~/.cache/shape-probe` +during planning. It is structural evidence for the test layout, not acceptance +evidence for the selected `0.19.1` dependency: + +```plaintext + Running unittests src/lib.rs +test profile::tests::an_unconfigured_app_omits_the_time_plugin ... ok +test profile::tests::minimal_app_adds_the_time_plugin ... ok +test profile::tests::minimal_app_counts_frames::case_1 ... ok +test profile::tests::add_minimal_plugins_matches_minimal_app ... ok +test profile::tests::minimal_app_counts_frames::case_2 ... ok +test result: ok. 5 passed; 0 failed + + Running tests/extraction_boundary.rs +test guard_ignores_game_names_outwith_dependency_tables ... ok +test guard_detects_a_directly_declared_game_crate ... ok +test guard_permits_the_test_macro_crate ... ok +test manifest_declares_no_game_crates ... ok +test result: ok. 4 passed; 0 failed + + Running tests/headless_scenario.rs +test minimal_app_advances_one_tick ... ok +test result: ok. 1 passed; 0 failed + + Running tests/tick_properties.rs +test frame_count_tracks_update_calls ... ok +test result: ok. 1 passed; 0 failed + + Doc-tests rstest_bdd_harness_bevy +test result: ok. 2 passed; 0 failed +``` + +`cargo clippy --workspace --all-targets --all-features` with warnings denied, +and `cargo doc --workspace --all-features --no-deps` with rustdoc warnings +denied, both complete clean on the same tree. This covers the two surfaces +earlier reviews flagged as unprobed: `clippy::needless_pass_by_value` does +**not** fire on the scenario function's by-value fixture parameter, and +`clippy::missing_assert_message` does **not** fire on `expect_that!`, +`assert_that!`, `prop_assert_eq!`, or `pretty_assertions`' equality macro — +those do not expand to the standard-library assert diagnostic items the lint +keys on. A bare `assert!` would fire; do not introduce one. + +### Evidence: the harness contract works under published beta3 + +A second probe implemented a minimal harness adapter with a non-unit context +wrapping a shared, interior-mutable application, selected it through the +`#[scenario]` macro's harness argument, and had steps take the context through +the reserved fixture key. It passed against published `0.6.0-beta3`, confirming +design §§4-5 is implementable without a git dependency: + +```plaintext +test harness_scenario ... ok +``` + +This is evidence for `0.5.1.2`, not work to be repeated here. + +### Evidence: the two lint traps + +```plaintext +error: used `expect()` on an `Option` value + --> tests/headless_scenario.rs:22:21 + = note: requested on the command line with `-D clippy::expect-used` + +error: unnecessary braces around block return value + --> tests/headless_scenario.rs:13:26 + = note: `-D unused-braces` implied by `-D warnings` +``` + +The first was resolved by replacing a thread-local `Option` slot with an +`rstest` fixture. The second is resolved by the layer beneath this one in the +stack: `#[allow_fixture_expansion_lints]` from `skyjoust-test-macros`, applied +above `#[fixture]`, which lets the fixture keep its natural single-expression +form. A plain non-fixture function with the same single-line shape does not +fire the lint, so the cause is the fixture macro's expansion — which is +precisely why the suppression belongs in a macro rather than at the call site. + +### Historical evidence: Bevy `0.17.3` feature selection and cost + +| Historical Bevy dependency declaration | Normal | With dev | +| ---------------------------------------------------------------------- | ------ | -------- | +| `{ version = "0.17.3", default-features = false }` | 115 | 259 | +| `{ version = "0.17.3", default-features = false, features = ["std"] }` | 139 | 279 | +| `"0.17.3"` (Bevy defaults) | 428 | n/a | +| `{ version = "0.19.1", default-features = false, features = ["std"] }` | 121 | 267 | + +*Table 1: Historical resolved crate counts for the full harness crate, measured +with `cargo tree --workspace -e normal --prefix none | sort -u | wc -l` and the +dev-inclusive equivalent. The 428 figure is from the Bevy-only probe. The final +row is the selected `0.19.1` graph, measured the same way against the real +workspace during Milestone 2; the isolated crate queries in +`Behavioural acceptance` report the same counts.* + +Measured build cost on six cores with a warm Cargo registry: + +```plaintext +COLD cranelift check --workspace --all-targets --all-features: 26s +COLD cranelift test build (--no-run): 30s +COLD llvm clippy --workspace --all-targets --all-features: 19s +WARM cranelift check: 0s +du -sh target: 2.2G +``` + +These figures prove only the discarded `0.17.3` baseline. The Milestone 0 +follow-up must establish that Cranelift compiles the `0.19.1` graph before the +plan relies on equivalent cost or compatibility claims. + +### Note on beta3 message wording + +Published `0.6.0-beta3` generated code panics with the message +`harness failed to initialise scenario`, where `main` uses the `-ize` form. Any +future test asserting on that string must match the version in use, and must +keep the literal inside backticks — the spelling gate rejects the `-ise` form +in bare prose. This milestone asserts on no such string. + +## Interfaces and dependencies + +### The manifest + +Create `crates/rstest-bdd-harness-bevy/Cargo.toml` exactly as follows. + +```toml +[package] +name = "rstest-bdd-harness-bevy" +version = "0.1.0" +edition = "2024" +description = "Headless Bevy harness adapter for rstest-bdd behavioural tests." +license = "ISC" +publish = false +readme = "README.md" + +[lints] +workspace = true + +[dependencies] +bevy = { version = "0.19.1", default-features = false, features = ["std"] } +# Exact pins: a caret requirement on a pre-release is not a pin. A caret on +# 0.6.0-beta3 admits 0.6.0-beta4, which changes StepContext borrowing. +rstest-bdd-harness = "=0.6.0-beta3" +tracing = "0.1" + +[dev-dependencies] +googletest = "0.14.3" +pretty_assertions = "1" +proptest = "1" +rstest = "0.26" +rstest-bdd = "=0.6.0-beta3" +rstest-bdd-macros = "=0.6.0-beta3" +skyjoust-test-macros = { path = "../skyjoust_test_macros" } +``` + +Notes on each entry: + +- `bevy` is a normal dependency because the library builds application values. + Both the disabled default features and the enabled `std` feature are + load-bearing; see `Risks` and `Decision log`. +- `rstest-bdd-harness` is a normal dependency, and must stay one. The + `#[scenario]` macro emits paths into that crate directly for third-party + harnesses — it recognizes only the Tokio and GPUI adapter crates as + first-party — and panics at expansion time if the crate is absent. A normal + dependency is visible to integration tests, which satisfies design §7's + requirement. Do not move it into development dependencies in the extracted + crate; every downstream scenario would break. +- `tracing` is direct so the re-export does not expose a version this crate + does not control. `0.5.1.2` needs it for design §6's diagnostic path. +- `rstest-bdd` and `rstest-bdd-macros` are development dependencies: the + crate's own behavioural tests use them, the library does not. +- `rstest 0.26` matches what `rstest-bdd 0.6.0-beta3` expects (which requires + `0.26.1` at minimum). After the rebase, the merged root and validator + manifests both carry `rstest = "0.26"`; this crate uses the same implicit + caret line so the workspace holds one `rstest` line. +- `skyjoust-test-macros` supplies `allow_fixture_expansion_lints`, without + which the scenario fixture cannot satisfy `make lint` and `make check-fmt` at + the same time. It is a path dependency on the crate added by the layer + beneath this one in the stack, and is dev-only: the library never uses it. It + is the one dependency here that does *not* travel to the extracted repository + — see the extraction note in + [ADR 006](../adr/006-test-macro-crate-for-fixture-expansion-lints.md). +- The publish and licence settings change at extraction, where the crate should + adopt the sibling `0.6.0-beta` numbering so its version states which harness + contract it targets. + +Add the member to the root `Cargo.toml`: + +```toml +[workspace] +members = [ + ".", + "crates/rstest-bdd-harness-bevy", + "crates/skyjoust_stateright_validator", +] +resolver = "3" +``` + +### The public API + +In `crates/rstest-bdd-harness-bevy/src/lib.rs`: + +```rust +//! Reusable headless Bevy harness scaffolding for `rstest-bdd` behavioural +//! tests. +//! +//! This crate incubates in the Skyjoust workspace but depends on no Skyjoust or +//! Lille code, so it can move to its own repository as a directory move plus a +//! dependency rewire. Game-specific setup belongs in downstream profile types, +//! never here. + +mod profile; + +pub use bevy; +pub use profile::{add_minimal_plugins, minimal_app}; +pub use rstest_bdd_harness::{ + AttributePolicy, HarnessAdapter, HarnessError, HarnessResult, ScenarioMetadata, + ScenarioRunRequest, ScenarioRunner, StdScenarioRunRequest, StdScenarioRunner, TestAttribute, +}; +pub use tracing; +``` + +The harness list matches `rstest-bdd-harness-gpui`'s re-exports exactly, +including the two unit-context aliases. Two items are deliberately omitted: +`StdHarness` and `DefaultAttributePolicy` are the base crate's *own* +implementations rather than contract types, and the GPUI adapter omits both — a +Bevy adapter re-exporting `StdHarness` would offer a harness that ignores Bevy +entirely. Do not "complete" the list with them. + +In `crates/rstest-bdd-harness-bevy/src/profile.rs`, these two functions must +exist at the end of Milestone 3: + +```rust +/// Adds the minimal headless plugin set to `app`. +pub fn add_minimal_plugins(app: &mut bevy::app::App); + +/// Builds a headless Bevy application carrying only the minimal plugin set. +#[must_use] +pub fn minimal_app() -> bevy::app::App; +``` + +`add_minimal_plugins` has the shape design §5's `configure` calls, so in +`0.5.1.2` `MinimalBevyProfile::configure` becomes two lines that call it. +`minimal_app` is the harness-free entry point for tests that want an +application without a scenario; both stay public. Remember that every type name +in the doc comments needs backticks — `clippy::doc_markdown` is denied. + +### The file layout + +Design §9's layout is the destination. This milestone creates the subset it +needs; `0.5.1.2` adds the context, harness, panic, and policy modules. Stage B +updates §9 to these names so the two do not diverge. + +```plaintext +crates/rstest-bdd-harness-bevy/ +|-- Cargo.toml +|-- README.md +|-- src/ +| |-- lib.rs +| |-- profile.rs +| `-- profile_tests.rs +`-- tests/ + |-- extraction_boundary.rs + |-- headless_scenario.rs + |-- tick_properties.rs + `-- features/ + `-- headless_scenario.feature +``` + +`profile_tests.rs` sits beside `profile.rs` and is wired in with the +repository's existing idiom, used at four sites in the validator crate: + +```rust +#[cfg(test)] +#[path = "profile_tests.rs"] +mod tests; +``` + +A sibling file moves with the directory, so it costs nothing at extraction. It +does mean unit tests live inside `src/` and would enter a published archive; +that is acceptable and worth one line in the crate README. + +### Test shapes + +Unit tests in `src/profile_tests.rs` use `#[gtest]` above `#[rstest]` so +`googletest` assertions have a test context: + +```rust +//! Unit tests for the headless plugin configuration. + +use bevy::{app::App, diagnostic::FrameCount, time::TimePlugin}; +use googletest::prelude::*; +use rstest::rstest; + +use super::{add_minimal_plugins, minimal_app}; + +#[gtest] +#[rstest] +#[case(0)] +#[case(3)] +fn minimal_app_counts_frames(#[case] ticks: u32) { + let mut app = minimal_app(); + (0..ticks).for_each(|_| app.update()); + expect_that!(app.world().resource::().0, eq(ticks)); +} +``` + +Alongside it: `minimal_app_adds_the_time_plugin`; +`an_unconfigured_app_omits_the_time_plugin`, the negative case — note the name +does not claim `App::new()` is plugin-free, because it is not; and +`add_minimal_plugins_matches_minimal_app`, proving the two entry points agree. +The parameterized frame-count test is retained as a fast non-property smoke +even though the property test subsumes it. + +`tests/headless_scenario.rs`, verbatim — this is the only genuinely novel +mechanism in the milestone, so it is given in full rather than sketched: + +```rust +//! Behavioural coverage proving `rstest-bdd` drives a headless Bevy app. + +use std::cell::RefCell; + +use bevy::{app::App, diagnostic::FrameCount, time::TimePlugin}; +use googletest::prelude::*; +use rstest::fixture; +use rstest_bdd::StepResult; +use rstest_bdd_harness_bevy::minimal_app; +use rstest_bdd_macros::{given, scenario, then, when}; +use skyjoust_test_macros::allow_fixture_expansion_lints; + +/// Gives `rustc` a rebuild dependency on the feature file; feature-file-only +/// edits do not otherwise invalidate the build. +const _: &str = include_str!("features/headless_scenario.feature"); + +/// Scenario-scoped headless application shared by the steps below. +#[allow_fixture_expansion_lints] +#[fixture] +fn app() -> RefCell { RefCell::new(minimal_app()) } + +#[given("a minimal headless Bevy application")] +fn given_minimal_app(app: &RefCell) -> StepResult<(), String> { + app.try_borrow() + .map(|shared| assert_that!(shared.is_plugin_added::(), eq(true))) + .map_err(|error| error.to_string()) +} + +#[when("the schedule advances once")] +fn when_schedule_advances_once(app: &RefCell) -> StepResult<(), String> { + app.try_borrow_mut() + .map(|mut borrow| borrow.update()) + .map_err(|error| error.to_string()) +} + +#[then("the frame count reads 1")] +fn then_frame_count_reads_one(app: &RefCell) -> StepResult<(), String> { + app.try_borrow() + .map(|shared| { + let observed = shared.world().resource::().0; + assert_that!(observed, eq(1_u32)); + }) + .map_err(|error| error.to_string()) +} + +#[scenario(path = "tests/features/headless_scenario.feature", index = 0)] +fn minimal_app_advances_one_tick(app: RefCell) {} +``` + +Three things a first reader will not guess. The fixture parameter must appear +on the `#[scenario]` function as well as on the steps, because that is how +`rstest` knows to construct it. And step functions use `assert_that!`, which +panics directly, rather than `expect_that!`, which needs a `#[gtest]` context +that generated step functions do not have. Finally, a step is not itself a +`#[test]` function, so `.expect()` is still denied there. The steps return +`StepResult` and transform `RefCell` borrow errors into normal step failures; +the functional chains keep mutation and error conversion adjacent. + +The property test in `tests/tick_properties.rs` states the invariant that +`0.5.1.2`'s `update_times` must preserve, with an explicit case count: + +```rust +proptest! { + #![proptest_config(ProptestConfig { cases: 32, ..ProptestConfig::default() })] + + #[test] + fn frame_count_tracks_update_calls(ticks in 0_u32..=32) { + let mut app = minimal_app(); + (0..ticks).for_each(|_| app.update()); + prop_assert_eq!(app.world().resource::().0, ticks); + } +} +``` + +`prop_assert_eq!` returns an error rather than panicking, keeping the test free +of denied panic-prone operations. + +The boundary test in `tests/extraction_boundary.rs` encodes the roadmap's +success criterion as a pure predicate, and is honest in its own documentation +about what it does not cover: + +```rust +//! Tripwire for the extraction contract: no game crate may be declared here. +//! +//! The check is textual and direct-only. It sees neither transitive edges nor +//! renamed packages; `cargo tree -p rstest-bdd-harness-bevy -e normal,dev` +//! remains the authority for the constraint as a whole. + +/// Crate names this harness must never declare, per the extraction contract. +const FORBIDDEN_CRATES: [&str; 3] = ["skyjoust", "skyjoust-stateright-validator", "lille"]; + +/// Returns the entries of [`FORBIDDEN_CRATES`] declared in `manifest`'s +/// dependency tables. +fn forbidden_dependencies(manifest: &str) -> Vec<&'static str>; +``` + +Three properties of the predicate are load-bearing, and each has a test. + +The `'static` lifetime is deliberate and correct: the returned names come from +the fixed list, not from `manifest`. + +Scope the scan to the dependency, development-dependency, and build-dependency +tables, not the whole file — otherwise a `repository` field naming the Skyjoust +remote would false-positive. + +Match dependency **names exactly**, taking the key to the left of `=` on each +entry line and comparing whole strings. A substring scan would reject +`skyjoust-test-macros`, which is a permitted development dependency (see +`Constraints`), and would equally be fooled by a comment. Exact matching keeps +`skyjoust = { path = "../.." }` caught while letting the tooling crate through. + +Four tests: `manifest_declares_no_game_crates`, against an `include_str!` of +the crate's own manifest; `guard_detects_a_directly_declared_game_crate`, +against a synthetic manifest, which is what proves the guard can fail; +`guard_ignores_game_names_outwith_dependency_tables`, which proves the scoping; +and `guard_permits_the_test_macro_crate`, which pins the exact-match behaviour +so a later "tightening" to substring matching fails loudly instead of silently +breaking the build. Use `pretty_assertions` for the vector comparisons so a +regression prints a readable diff. + +Automating the transitive check would need `cargo_metadata` as a development +dependency and a nested `cargo` invocation inside `make test`; both are +declined here as disproportionate, and the manual `cargo tree` step in +`Behavioural acceptance` carries that half. Automating it is follow-up work for +`0.5.1.5`. + +### Files this plan creates or changes + +| Path | Change | +| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `Cargo.toml` | Add the workspace member. | +| `Cargo.lock` | Regenerated for the measured Bevy `0.19.1` graph. | +| `docs/adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md` | New ADR. | +| `docs/adr/002-crate-layout-and-public-api.md` | Add a forward pointer to ADR 007 in Consequences. | +| `crates/rstest-bdd-harness-bevy/Cargo.toml` (dev-dependency) | Path dependency on `skyjoust-test-macros`. | +| `docs/rstest-bdd-harness-bevy-design.md` | Amend sections 3, 9 (dependencies and layout), and 13. | +| `docs/contents.md` | Index ADR 007 and this ExecPlan. | +| `docs/repository-layout.md` | Tree sketch, path responsibilities, workspace membership note. | +| `docs/developers-guide.md` | Amend section 2; add a harness-crate conventions section. | +| `docs/roadmap.md` | Target Bevy `0.19.1`; mark 0.5.1.1 done; correct dependency guidance. | +| `docs/execplans/0-5-1-1-add-rstest-bdd-harness-bevy-workspace-member.md` | This living document, updated as work proceeds. | +| `crates/rstest-bdd-harness-bevy/Cargo.toml` | New crate manifest. | +| `crates/rstest-bdd-harness-bevy/README.md` | New crate README with the Bevy compatibility table. | +| `crates/rstest-bdd-harness-bevy/src/lib.rs` | New crate root. | +| `crates/rstest-bdd-harness-bevy/src/profile.rs` | New plugin configuration module. | +| `crates/rstest-bdd-harness-bevy/src/profile_tests.rs` | New sibling unit tests. | +| `crates/rstest-bdd-harness-bevy/tests/headless_scenario.rs` | New behavioural binding. | +| `crates/rstest-bdd-harness-bevy/tests/features/headless_scenario.feature` | New feature file. | +| `crates/rstest-bdd-harness-bevy/tests/tick_properties.rs` | New property test. | +| `crates/rstest-bdd-harness-bevy/tests/extraction_boundary.rs` | New boundary tripwire. | +| `crates/rstest-bdd-harness-bevy/tests/proptest-regressions/` | Created only if the property test ever fails; committed. | +| `typos.local.toml` and `typos.toml` | Only if spelling rejects new vocabulary; regenerate both. | +| `docs/users-guide.md`, `docs/development-plan.md`, `docs/skyjoust-technical-design.md` | No change — see `Decision log`. | + +*Table 2: The authoritative file manifest. The scope trigger in `Tolerances` is +measured against this table.* + +## Revision note + +Revision 2, 2026-08-15. Revised after a six-lens design review, and after four +further probes run to check the reviewers' claims rather than accept them. + +What changed and why: + +- **Manifest.** Licence corrected to ISC, matching the repository `LICENSE` and + every published sibling. Marked the crate unpublishable so the incubating + copy cannot claim the upstream-documented crate name. Switched to exact + requirements, because a caret on a pre-release admits the `beta4` that this + plan itself documents as breaking. Added Bevy's `std` feature and `tracing` + as a direct dependency. +- **Public API.** The two constructors became `add_minimal_plugins` and + `minimal_app`. The first draft claimed the constructors would become + `configure` bodies; they could not, because `configure` mutates a borrowed + application. `add_minimal_plugins` has that shape, so the carry-forward claim + is now true. Added the Bevy re-export and the two unit-context aliases the + cited GPUI precedent actually re-exports. +- **Milestone 2.** The specified red state could not occur: without the module + declaration, rustc never compiles the test file and the run passes with zero + tests. The declaration moved into the red state; only the re-export is + withheld. +- **Gate commands.** All milestone test commands now carry denied warnings and + the dev-fast configuration, matching what `make` does — the absence of the + former is precisely why the first draft's probes missed the `unused_braces` + trap. Replaced an inert log-path variable with a `gate` helper that + propagates the pipeline's exit status, so a failing gate no longer reads as + passing through `tee`. +- **New risks recorded.** The fixture deadlock between `unused_braces` and + single-line formatting; the timestamp-counter clock without Bevy's `std` + feature; workspace-wide feature unification defeating the headless + constraint; the CI cache budget; and the floating-versus-pinned rustfmt split + between `make fmt` and `make check-fmt`. +- **Added `Outcomes & retrospective`**, which the document's own opening + paragraph required and the first draft omitted, and a section then named + `Open decisions requiring approval` for the two questions the review could + not settle from the repository. +- **Corrected figures.** The graph is 139 crates normal and 279 with + development dependencies, not 98; that figure was the bevy-only probe. Build + cost is now measured in seconds and gigabytes rather than asserted in + adjectives. Coverage is met by the unit tests, not the doctests, because the + CI coverage action uses nextest, which does not run them. +- **Scope trigger** raised from 18 files to 22, because the first draft's + tolerance was set to exactly the file count it specified, and Table 2 became + the authoritative manifest it is measured against. + +Revision 3, 2026-08-15. Adopted the estate-mandated remedy for the fixture +expansion lint, which now lands in the layer beneath this plan in the pull +request stack rather than being worked around locally. + +- The `let`-binding workaround is withdrawn. The scenario fixture keeps its + natural single-expression form and carries `#[allow_fixture_expansion_lints]` + from `skyjoust-test-macros`, mirroring `weaver-test-macros` in + `leynos/weaver`. The workaround would have spread a bespoke idiom across + every fixture the project writes instead of fixing the cause once. +- The harness ADR is renumbered 006 to 007, because the lower layer takes 006 + for the test-macro decision. +- `skyjoust-test-macros` is added as a development dependency, and the + extraction contract in `Constraints` is restated to distinguish game code, + which is forbidden, from test-only tooling, which is not. +- The boundary guard now matches dependency names exactly rather than by + substring, and the `cargo tree` acceptance pattern anchors on the trailing + space. Both changes exist for the same reason: `skyjoust-test-macros` + contains the substring `skyjoust`, so the guard as previously specified would + have rejected a dependency the contract allows. A fourth boundary test pins + that behaviour. + +Revision 4, 2026-08-17. Recorded the maintainer's answers to the open design +questions and reconciled their effects across the complete plan. + +- Bevy now targets `0.19.1` with implicit caret syntax. All `0.17.3` probe + results are labelled historical, and a Milestone 0 follow-up must repeat the + API, feature, headless, Cranelift, lint, test, and cost checks before code + work begins. +- The `rstest-bdd` family remains exactly pinned to `=0.6.0-beta3`. A later beta + or final release is only a migration trigger; the plan requires a green + compatibility pass before adopting it and returning to implicit caret syntax. +- Step functions now return `StepResult`, propagate `RefCell` borrow failures, + and use functional transformations where those remain clear. This records why + `.expect()` is invalid in macro-registered step functions even though test + bodies may use it. +- Pull request #6 is a prerequisite: wait for automerge, rebase the stack onto + `main`, and reconcile the new crate's `rstest` requirement with the merged + root before Milestone 2. +- Added the mandatory `Conformance basis` and `Verification plan` living + sections, including trace links, negative controls, external axioms, and + discharge conditions for the new decisions. + +Revision 5, 2026-08-21. Rebasing the implementation branch onto the pull +request's current `origin/main` base required a single `Cargo.lock` conflict. + +- Retained `main`'s lockfile at the conflict, then regenerated it from the + final manifests. The regenerated graph resolves Bevy `0.19.1` and the + inspected `rstest-bdd` family at `0.6.0-beta3`. +- Repeated every repository gate and every ExecPlan acceptance command. The + feature-file freshness A/B was rerun rather than inferred from earlier + evidence. +- Replaced the obsolete force-push authorization blocker with the remaining + remote-delivery condition. The branch may be published with the maintainer's + explicit lease-protected authorization, but PR #51 remains pending until its + merge state is clean and remote CI is non-skipped. diff --git a/docs/repository-layout.md b/docs/repository-layout.md index 45ce95d..3e92a44 100644 --- a/docs/repository-layout.md +++ b/docs/repository-layout.md @@ -16,6 +16,7 @@ the contributor orientation stays synchronized with the repository layout. . |-- .github/ |-- crates/ +| |-- rstest-bdd-harness-bevy/ | |-- skyjoust_stateright_validator/ | `-- skyjoust_test_macros/ |-- docs/ @@ -40,8 +41,9 @@ _Figure 1: Simplified repository tree for contributor orientation._ and keep edited guidance tied to the relevant `make` target or project document. - `Cargo.toml`: workspace root and root package configuration. The workspace - currently includes `.`, `crates/skyjoust_stateright_validator`, and - `crates/skyjoust_test_macros`. Run the Rust gates after workspace changes. + currently includes `.`, `crates/rstest-bdd-harness-bevy`, + `crates/skyjoust_stateright_validator`, and `crates/skyjoust_test_macros`. + Run the Rust gates after workspace changes. - `Makefile`: canonical contributor command surface for build, formatting, linting, tests, Markdown checks, diagram validation, and state graph regeneration. Prefer these targets over direct tool invocations. @@ -77,6 +79,20 @@ _Figure 1: Simplified repository tree for contributor orientation._ published. Add an attribute here only when a lint comes from an expansion rather than from handwritten code; see [ADR 006](adr/006-test-macro-crate-for-fixture-expansion-lints.md). +- `crates/rstest-bdd-harness-bevy/`: reusable Bevy harness scaffolding for + `rstest-bdd` behavioural tests. It incubates in-tree but depends on no + Skyjoust or Lille game code, so it can move to its own repository as a + directory move plus a dependency rewire. See + [ADR 007](adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md) and + [the harness design](rstest-bdd-harness-bevy-design.md). +- `crates/rstest-bdd-harness-bevy/src/`: headless plugin configuration. + `profile.rs` owns `add_minimal_plugins` and `minimal_app`; `profile_tests.rs` + holds the sibling unit tests. The harness types arrive with roadmap task + `0.5.1.2`. +- `crates/rstest-bdd-harness-bevy/tests/`: integration coverage for the + scaffold — the Gherkin scenario binding, the tick-count property test, and + the extraction-contract tripwire. Keep the feature file in `tests/features/` + and the `include_str!` rebuild guard with its binding. - `docs/`: source of truth for product requirements, technical design, guides, standards, state graph references, and generated diagrams. Update the relevant document when requirements, architecture, workflows, or generated diff --git a/docs/roadmap.md b/docs/roadmap.md index 2a3153f..ff96afa 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -38,11 +38,11 @@ informs later runtime testing in Phase 1. See `docs/rstest-bdd-harness-bevy-design.md` §§4-10 and `docs/skyjoust-technical-design.md` §§4, 5, and 14. -- [ ] 0.5.1.1. Add `crates/rstest-bdd-harness-bevy` as a workspace member. +- [x] 0.5.1.1. Add `crates/rstest-bdd-harness-bevy` as a workspace member. - Keep the crate free of Skyjoust and Lille dependencies. - - Use Bevy `0.17.3` with `default-features = false`. - - Use git `rstest-bdd` dependencies against `main` until v0.6.0-beta3 is - published. + - Use Bevy `0.19.1` with `default-features = false` and the `std` feature; + ordinary stable requirements use implicit caret syntax while the + `rstest-bdd` family remains exactly pinned to `=0.6.0-beta3`. - See `docs/rstest-bdd-harness-bevy-design.md` §§3 and 9. - Success: the crate can later move to its own repository without deleting game-specific code. diff --git a/docs/rstest-bdd-harness-bevy-design.md b/docs/rstest-bdd-harness-bevy-design.md index 4b5c473..ae8a045 100644 --- a/docs/rstest-bdd-harness-bevy-design.md +++ b/docs/rstest-bdd-harness-bevy-design.md @@ -25,12 +25,13 @@ design already assigns ECS schedules and state resources to Bevy, while `winit` and `pixels` own windowing and presentation for the Minimum Viable Product (MVP). -`rstest-bdd` HEAD provides the harness extension points needed for this shape: +`rstest-bdd` provides the harness extension points needed for this shape: `HarnessAdapter`, associated `Context`, `ScenarioRunRequest`, `ScenarioRunner`, -`HarnessResult`, `ScenarioMetadata`, `AttributePolicy`, and `TestAttribute`. -The public manifest still labels those workspace crates as `0.6.0-beta2`, but -the branch head inspected for this design is commit `21b67a4`, which carries -the v0.6.0-beta3 harness API targeted by this work. +`HarnessResult`, `ScenarioMetadata`, `AttributePolicy`, and `TestAttribute`. The +`0.6.0-beta3` releases carrying that API were published to crates.io on +2026-07-07; upstream `main` has since moved to an unpublished `0.6.0-beta4`. See +[ADR 007](adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md) for the +dependency decision and its evidence. The design also needs to remain extractable. Skyjoust should incubate the crate in-tree because that keeps review and validation local while the first runtime @@ -81,11 +82,16 @@ when a step panics, and resumes unwinding with an augmented panic message. The Bevy harness should mirror the diagnostic pattern without inheriting GPUI's single-threaded runtime constraints. -Bevy 0.17.3 documents `App` as the primary application API for plugin setup and -the standard lifecycle. It documents `MinimalPlugins` as the minimal plugin -group for a Bevy application, including task pools, time, frame count, and the -schedule runner. That matches the headless behavioural test need better than a -raw `World`. +Bevy 0.19.1 documents `App` as the primary application API for plugin setup and +the standard lifecycle, and `MinimalPlugins` as the minimal plugin group for a +Bevy application, including task pools, time, frame count, and the schedule +runner. That matches the headless behavioural test need better than a raw +`World`. The harness declares Bevy with `default-features = false` plus +`features = ["std"]`: under the earlier `0.17.3` probe, omitting `std` silently +degraded the time plugin to a fallback clock running at the timestamp counter +frequency, so `std` is load-bearing for real time without enabling the renderer +or window. The same check is repeated against `0.19.1` in the ExecPlan's +Milestone 0 follow-up. ## 4. Architecture @@ -271,27 +277,36 @@ The Skyjoust incubation crate should use this layout: ```plaintext crates/rstest-bdd-harness-bevy/ |-- Cargo.toml +|-- README.md |-- src/ | |-- lib.rs -| |-- context.rs -| |-- harness.rs -| |-- panic.rs -| |-- policy.rs -| `-- profile.rs +| |-- profile.rs +| `-- profile_tests.rs `-- tests/ - |-- bevy_scenario.rs - |-- panic_diagnostics.rs - |-- profile_hooks.rs + |-- extraction_boundary.rs + |-- headless_scenario.rs + |-- tick_properties.rs `-- features/ - `-- bevy_scenario.feature + `-- headless_scenario.feature ``` -The first compatibility line should target Bevy 0.17.3 with -`default-features = false`, because `bevy::prelude::*` is the natural import -surface for downstream steps and the default harness must stay headless. Until -`rstest-bdd` publishes v0.6.0-beta3, the harness crate should use git -dependencies against `https://github.com/leynos/rstest-bdd` `main`. After -publication, replace those with normal `0.6.0-beta3` crate dependencies. +Roadmap task `0.5.1.1` establishes exactly the subset above. Task `0.5.1.2` adds +`src/context.rs`, `src/harness.rs`, `src/panic.rs`, and `src/policy.rs` and +their behavioural test pairs; the feature file `headless_scenario.feature` is +re-bound through the harness-selecting `#[scenario]` rather than copied. + +The compatibility line targets Bevy `0.19.1` with `default-features = false` and +`features = ["std"]`: `bevy::prelude::*` is the natural import surface for +downstream steps, the default harness must stay headless, and the `std` feature +keeps the time plugin on wall-clock without enabling the renderer or window. All +`rstest-bdd` family requirements are exact `=0.6.0-beta3` pins against the +published crates; do not use git dependencies against `main`, whose unpublished +`0.6.0-beta4` changes `StepContext` borrowing and adds a policy-conformance +module and a `testing` feature absent from `beta3`. When a later beta or +`0.6.0` final publishes, test the whole `rstest-bdd` family together before +changing the exact pins; adopt the policy-conformance helper in `0.5.1.3` only +after that compatibility pass. If `0.5.1.3` reaches the policy-conformance task +first, escalate rather than adding a git dependency. ## 10. Verification strategy @@ -352,13 +367,15 @@ state resources unless they are represented as downstream profile code. ## 13. References -- `rstest-bdd` repository HEAD, commit `21b67a4`, inspected on - 2026-06-27: . +- `rstest-bdd` 0.6.0-beta3 crate releases, published 2026-07-07: + . - `rstest-bdd` third-party harness cookbook and GPUI harness documentation: . -- Bevy 0.17.3 `App` documentation: - . -- Bevy 0.17.3 `MinimalPlugins` documentation: - . +- Bevy 0.19.1 `App` documentation: + . +- Bevy 0.19.1 `MinimalPlugins` documentation: + . - Skyjoust runtime ownership and testing constraints: [Project Skyjoust technical design](skyjoust-technical-design.md). +- In-tree incubation and dependency decisions: + [ADR 007](adr/007-in-tree-incubation-of-the-bevy-bdd-harness-crate.md).