diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-23 21:32:50 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-23 21:32:50 +0300 |
| commit | 9cc24e715db81edbe21c0d04aadd00f11dddecb8 (patch) | |
| tree | 08a1262dea86bcd7ec58c6494cedd001c45a78fe | |
| parent | f8e447ffee746cfe6580cc0e78a8a225aa39b546 (diff) | |
| download | fparkan-9cc24e715db81edbe21c0d04aadd00f11dddecb8.tar.xz fparkan-9cc24e715db81edbe21c0d04aadd00f11dddecb8.zip | |
fix: close stage 0-2 synthetic gates
38 files changed, 4030 insertions, 1729 deletions
@@ -3,18 +3,293 @@ version = 4 [[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.13.0", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.18", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "camino" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-util-schemas" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830" +dependencies = [ + "semver", + "serde", + "serde-untagged", + "serde-value", + "thiserror 2.0.18", + "toml", + "unicode-xid", + "url", +] + +[[package]] +name = "cargo_metadata" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cfca2aaa699835ba88faf58a06342a314a950d2b9686165e038286c30316868" +dependencies = [ + "camino", + "cargo-platform", + "cargo-util-schemas", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -24,6 +299,56 @@ dependencies = [ ] [[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -33,6 +358,39 @@ dependencies = [ ] [[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 = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] name = "flate2" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -43,6 +401,42 @@ dependencies = [ ] [[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] name = "fparkan-animation" version = "0.1.0" @@ -51,11 +445,14 @@ name = "fparkan-assets" version = "0.1.0" dependencies = [ "fparkan-material", + "fparkan-mission-format", "fparkan-msh", "fparkan-nres", "fparkan-path", "fparkan-prototype", "fparkan-resource", + "fparkan-terrain", + "fparkan-terrain-format", "fparkan-texm", "fparkan-vfs", ] @@ -68,11 +465,11 @@ version = "0.1.0" name = "fparkan-cli" version = "0.1.0" dependencies = [ + "fparkan-assets", "fparkan-corpus", - "fparkan-nres", + "fparkan-inspection", "fparkan-prototype", "fparkan-resource", - "fparkan-rsli", "fparkan-runtime", "fparkan-vfs", ] @@ -82,13 +479,25 @@ name = "fparkan-corpus" version = "0.1.0" dependencies = [ "fparkan-binary", + "fparkan-fx", + "fparkan-material", + "fparkan-mission-format", + "fparkan-msh", "fparkan-nres", "fparkan-path", + "fparkan-prototype", + "fparkan-rsli", + "fparkan-terrain-format", + "fparkan-texm", ] [[package]] name = "fparkan-diagnostics" version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] [[package]] name = "fparkan-fx" @@ -102,7 +511,11 @@ dependencies = [ name = "fparkan-game" version = "0.1.0" dependencies = [ + "fparkan-assets", + "fparkan-platform", + "fparkan-platform-winit", "fparkan-render", + "fparkan-render-vulkan", "fparkan-runtime", "fparkan-vfs", "fparkan-world", @@ -118,6 +531,19 @@ dependencies = [ ] [[package]] +name = "fparkan-inspection" +version = "0.1.0" +dependencies = [ + "fparkan-msh", + "fparkan-nres", + "fparkan-resource", + "fparkan-rsli", + "fparkan-terrain-format", + "fparkan-texm", + "fparkan-vfs", +] + +[[package]] name = "fparkan-material" version = "0.1.0" dependencies = [ @@ -162,10 +588,11 @@ name = "fparkan-platform" version = "0.1.0" [[package]] -name = "fparkan-platform-sdl" +name = "fparkan-platform-winit" version = "0.1.0" dependencies = [ "fparkan-platform", + "winit", ] [[package]] @@ -174,12 +601,9 @@ version = "0.1.0" dependencies = [ "encoding_rs", "fparkan-binary", - "fparkan-material", - "fparkan-msh", "fparkan-nres", "fparkan-path", "fparkan-resource", - "fparkan-texm", "fparkan-vfs", ] @@ -191,9 +615,10 @@ dependencies = [ ] [[package]] -name = "fparkan-render-gl" +name = "fparkan-render-vulkan" version = "0.1.0" dependencies = [ + "fparkan-platform", "fparkan-render", ] @@ -219,15 +644,12 @@ dependencies = [ name = "fparkan-runtime" version = "0.1.0" dependencies = [ - "fparkan-mission-format", - "fparkan-nres", + "fparkan-assets", "fparkan-path", "fparkan-platform", "fparkan-prototype", "fparkan-render", "fparkan-resource", - "fparkan-terrain", - "fparkan-terrain-format", "fparkan-vfs", "fparkan-world", ] @@ -274,14 +696,8 @@ dependencies = [ name = "fparkan-viewer" version = "0.1.0" dependencies = [ - "fparkan-msh", - "fparkan-nres", + "fparkan-inspection", "fparkan-render", - "fparkan-resource", - "fparkan-rsli", - "fparkan-terrain-format", - "fparkan-texm", - "fparkan-vfs", ] [[package]] @@ -292,6 +708,329 @@ dependencies = [ ] [[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "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.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.3.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", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "bitflags 2.13.0", + "libc", + "plain", + "redox_syscall 0.8.1", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -302,14 +1041,1470 @@ dependencies = [ ] [[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.12+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] name = "simd-adler32" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.13.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.13.0", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.13.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.13.0", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] name = "xtask" version = "0.1.0" dependencies = [ + "cargo_metadata", "fparkan-corpus", + "serde", + "toml", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", ] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/adapters/fparkan-platform-winit/src/lib.rs b/adapters/fparkan-platform-winit/src/lib.rs index ec30908..496d955 100644 --- a/adapters/fparkan-platform-winit/src/lib.rs +++ b/adapters/fparkan-platform-winit/src/lib.rs @@ -1,15 +1,34 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Minimal `winit`-backed platform adapter shim. use fparkan_platform::{ - EventSource, MonotonicClock, MonotonicInstant, PlatformEvent, PlatformError, PhysicalSize, + EventSource, MonotonicClock, MonotonicInstant, PhysicalSize, PlatformError, PlatformEvent, RenderRequest, WindowHandle, WindowPort, }; -use winit::event::{MouseButton, WindowEvent}; -use winit::event_loop::Event; use std::collections::VecDeque; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; +use winit::event::{Event, MouseButton, WindowEvent}; +use winit::platform::scancode::PhysicalKeyExtScancode; use winit::window::Window; static NEXT_WINDOW_HANDLE_ID: AtomicU64 = AtomicU64::new(1); @@ -52,7 +71,7 @@ impl WinitEventSource { } /// Pushes a mapped native window event. - pub fn push_window_event(&mut self, event: &WindowEvent<'_>) { + pub fn push_window_event(&mut self, event: &WindowEvent) { match event { WindowEvent::KeyboardInput { event, .. } => { self.queue.push_back(PlatformEvent::KeyboardInput { @@ -81,14 +100,13 @@ impl WinitEventSource { }); } WindowEvent::Focused(focused) => { - self.queue.push_back(PlatformEvent::FocusChanged { focused: *focused }); - } - WindowEvent::ScaleFactorChanged { - scale_factor, - .. - } => { self.queue - .push_back(PlatformEvent::DpiChanged { scale: *scale_factor }); + .push_back(PlatformEvent::FocusChanged { focused: *focused }); + } + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + self.queue.push_back(PlatformEvent::DpiChanged { + scale: *scale_factor, + }); } WindowEvent::CloseRequested => { self.queue.push_back(PlatformEvent::QuitRequested); @@ -98,7 +116,7 @@ impl WinitEventSource { } /// Pushes events from an event loop event. - pub fn push_event<T>(&mut self, event: &Event<'_, T>) { + pub fn push_event<T>(&mut self, event: &Event<T>) { if let Event::WindowEvent { event, .. } = event { self.push_window_event(event); } @@ -112,7 +130,7 @@ fn mouse_button_code(button: MouseButton) -> u16 { MouseButton::Middle => 2, MouseButton::Back => 3, MouseButton::Forward => 4, - MouseButton::Other(index) => 100 + u16::try_from(index).unwrap_or(0), + MouseButton::Other(index) => 100 + index, } } @@ -219,7 +237,10 @@ mod tests { source.push(PlatformEvent::QuitRequested); let mut events = Vec::new(); source.poll(&mut events)?; - assert_eq!(events, vec![PlatformEvent::Resumed, PlatformEvent::QuitRequested]); + assert_eq!( + events, + vec![PlatformEvent::Resumed, PlatformEvent::QuitRequested] + ); Ok(()) } @@ -227,8 +248,17 @@ mod tests { fn window_port_reports_default_request_profile() { let window = WinitWindow::synthetic(640, 360); let request = WinitWindow::default_render_request(); - assert_eq!(request.presentation, fparkan_platform::PresentationMode::Fifo); - assert_eq!(window.drawable_size(), PhysicalSize { width: 640, height: 360 }); + assert_eq!( + request.presentation, + fparkan_platform::PresentationMode::Fifo + ); + assert_eq!( + window.drawable_size(), + PhysicalSize { + width: 640, + height: 360 + } + ); } #[test] diff --git a/adapters/fparkan-render-vulkan/src/lib.rs b/adapters/fparkan-render-vulkan/src/lib.rs index 3d4f44d..6cae797 100644 --- a/adapters/fparkan-render-vulkan/src/lib.rs +++ b/adapters/fparkan-render-vulkan/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] #![deny(unsafe_op_in_unsafe_fn)] //! Vulkan adapter facade and migration-ready backend surface contract. //! @@ -8,10 +27,10 @@ //! //! This crate is the declared low-level Vulkan boundary. +use fparkan_platform::RenderRequest; use fparkan_render::{ canonical_capture, FrameOutput, RenderBackend, RenderCommandList, RenderError, }; -use fparkan_platform::RenderRequest; use std::time::{SystemTime, UNIX_EPOCH}; /// Vulkan backend migration readiness. @@ -120,7 +139,10 @@ impl VulkanBackend { impl RenderBackend for VulkanBackend { fn execute(&mut self, commands: &RenderCommandList) -> Result<FrameOutput, RenderError> { - if !matches!(self.state, VulkanBackendState::Ready | VulkanBackendState::Degraded) { + if !matches!( + self.state, + VulkanBackendState::Ready | VulkanBackendState::Degraded + ) { return Err(RenderError::InvalidRange); } let capture = canonical_capture(commands)?; diff --git a/apps/fparkan-cli/Cargo.toml b/apps/fparkan-cli/Cargo.toml index 90b26da..76d319a 100644 --- a/apps/fparkan-cli/Cargo.toml +++ b/apps/fparkan-cli/Cargo.toml @@ -6,6 +6,7 @@ license.workspace = true repository.workspace = true [dependencies] +fparkan-assets = { path = "../../crates/fparkan-assets" } fparkan-corpus = { path = "../../crates/fparkan-corpus" } fparkan-prototype = { path = "../../crates/fparkan-prototype" } fparkan-inspection = { path = "../../crates/fparkan-inspection" } diff --git a/apps/fparkan-cli/src/main.rs b/apps/fparkan-cli/src/main.rs index 043a21c..b86486c 100644 --- a/apps/fparkan-cli/src/main.rs +++ b/apps/fparkan-cli/src/main.rs @@ -1,11 +1,30 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] #![allow(clippy::print_stderr, clippy::print_stdout)] //! `FParkan` command-line tools. +use fparkan_assets::extend_graph_report_with_visual_dependencies; use fparkan_corpus::{discover, render_report_json, report, DiscoverOptions}; use fparkan_inspection::inspect_archive_file; use fparkan_inspection::ArchiveInspection; -use fparkan_assets::extend_graph_report_with_visual_dependencies; use fparkan_prototype::build_prototype_graph_report; use fparkan_resource::{resource_name, CachedResourceRepository}; use fparkan_runtime::{ @@ -135,12 +154,7 @@ fn inspect_prototype(args: &[String]) -> Result<(), String> { let roots = [resource_name(key.as_bytes())]; let (graph, resolved, mut report) = build_prototype_graph_report(&repository, vfs.as_ref(), &roots); - extend_graph_report_with_visual_dependencies( - &repository, - &mut report, - &graph, - &resolved, - ); + extend_graph_report_with_visual_dependencies(&repository, &mut report, &graph, &resolved); println!("{}", prototype_inspect_json(&key, &graph, &report)); Ok(()) } @@ -234,7 +248,9 @@ fn inspect_archive(args: &[String]) -> Result<(), String> { ); Ok(()) } - ArchiveInspection::Unsupported => Err(format!("{}: unsupported archive magic", path.display())), + ArchiveInspection::Unsupported => { + Err(format!("{}: unsupported archive magic", path.display())) + } } } diff --git a/apps/fparkan-game/Cargo.toml b/apps/fparkan-game/Cargo.toml index bac3397..a134a8b 100644 --- a/apps/fparkan-game/Cargo.toml +++ b/apps/fparkan-game/Cargo.toml @@ -6,6 +6,8 @@ license.workspace = true repository.workspace = true [dependencies] +fparkan-assets = { path = "../../crates/fparkan-assets" } +fparkan-platform = { path = "../../crates/fparkan-platform" } fparkan-render = { path = "../../crates/fparkan-render" } fparkan-platform-winit = { path = "../../adapters/fparkan-platform-winit" } fparkan-render-vulkan = { path = "../../adapters/fparkan-render-vulkan" } diff --git a/apps/fparkan-game/src/main.rs b/apps/fparkan-game/src/main.rs index 7ea7d0e..6f132e5 100644 --- a/apps/fparkan-game/src/main.rs +++ b/apps/fparkan-game/src/main.rs @@ -1,16 +1,37 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] #![allow(clippy::print_stderr, clippy::print_stdout)] //! `FParkan` rendered game composition root. +use fparkan_assets::PreparedVisual; +use fparkan_platform::WindowPort; +use fparkan_platform_winit::WinitWindow; use fparkan_render::{ - DrawCommand, DrawId, GpuMaterialId, GpuMeshId, IndexRange, RenderBackend, - RenderCommand, RenderCommandList, RenderPhase, + DrawCommand, DrawId, GpuMaterialId, GpuMeshId, IndexRange, RenderBackend, RenderCommand, + RenderCommandList, RenderPhase, }; -use fparkan_platform_winit::WinitWindow; use fparkan_render_vulkan::VulkanBackend; use fparkan_runtime::{ - create, frame, load_mission, EngineConfig, EngineMode, EngineServices, MissionRequest, - MissionAssets, loaded_mission_assets, + create, frame, load_mission, loaded_mission_assets, EngineConfig, EngineMode, EngineServices, + MissionAssets, MissionRequest, }; use fparkan_vfs::DirectoryVfs; use fparkan_world::WorldSnapshot; @@ -89,6 +110,7 @@ fn run(args: &[String]) -> Result<String, String> { )) } +#[cfg(test)] fn render_snapshot_commands(snapshot: &WorldSnapshot) -> RenderCommandList { render_snapshot_commands_with_assets(snapshot, None) } @@ -115,8 +137,10 @@ fn render_snapshot_commands_with_assets( GpuMeshId(u64::from(handle.slot) + 1) }; let material = prepared - .and_then(|visual| visual.primary_material_id()) - .map_or(GpuMaterialId(1), |material_id| GpuMaterialId(material_id.raw())); + .and_then(PreparedVisual::primary_material_id) + .map_or(GpuMaterialId(1), |material_id| { + GpuMaterialId(material_id.raw()) + }); let draw_id = snapshot .tick .0 diff --git a/apps/fparkan-headless/src/main.rs b/apps/fparkan-headless/src/main.rs index b78a7dc..c28b51b 100644 --- a/apps/fparkan-headless/src/main.rs +++ b/apps/fparkan-headless/src/main.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] #![allow(clippy::print_stderr, clippy::print_stdout)] //! `FParkan` headless runtime entrypoint. diff --git a/apps/fparkan-viewer/src/main.rs b/apps/fparkan-viewer/src/main.rs index ee96ab5..783ef92 100644 --- a/apps/fparkan-viewer/src/main.rs +++ b/apps/fparkan-viewer/src/main.rs @@ -1,10 +1,29 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] #![allow(clippy::print_stderr, clippy::print_stdout)] //! `FParkan` asset viewer composition root. use fparkan_inspection::{ - inspect_land_file, inspect_model_from_root, inspect_texture_from_root, ArchiveInspection, LandFileKind, - MapInspection, NresEntrySummary, + inspect_land_file, inspect_model_from_root, inspect_texture_from_root, ArchiveInspection, + LandFileKind, MapInspection, NresEntrySummary, }; use fparkan_render::{ build_commands, CameraSnapshot, DrawId, GpuMaterialId, GpuMeshId, IndexRange, RenderPhase, @@ -151,7 +170,11 @@ fn inspect_map(args: &[String]) -> Result<String, String> { }, )?; - Ok(render_map_inspection_json(&file.display().to_string(), &kind, &inspection)) + Ok(render_map_inspection_json( + &file.display().to_string(), + &kind, + &inspection, + )) } fn render_map_inspection_json(path: &str, kind: &str, inspection: &MapInspection) -> String { diff --git a/crates/fparkan-animation/src/lib.rs b/crates/fparkan-animation/src/lib.rs index 53111f3..7903f88 100644 --- a/crates/fparkan-animation/src/lib.rs +++ b/crates/fparkan-animation/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] #![allow(clippy::cast_precision_loss)] //! Deterministic animation sampling contracts. //! diff --git a/crates/fparkan-assets/src/lib.rs b/crates/fparkan-assets/src/lib.rs index f4501ee..0cbd3d4 100644 --- a/crates/fparkan-assets/src/lib.rs +++ b/crates/fparkan-assets/src/lib.rs @@ -1,15 +1,31 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Asset manager ports and transactional preparation models. use fparkan_material::{decode_wear, resolve_material, MaterialError, WEAR_KIND}; +use fparkan_mission_format::{decode_tma, decode_tma_land_path}; +pub use fparkan_mission_format::{LpString, MissionDocument, MissionError, TmaProfile}; use fparkan_msh::{decode_msh, validate_msh, MshError}; -pub use fparkan_nres::{NresDocument, NresError}; use fparkan_nres::{decode as decode_nres, ReadProfile}; -pub use fparkan_mission_format::{LpString, MissionDocument, MissionError, TmaProfile}; -pub use fparkan_terrain::{TerrainError, TerrainWorld}; -pub use fparkan_terrain_format::{BuildCategory, TerrainFormatError}; -use fparkan_mission_format::{decode_tma, decode_tma_land_path}; -use fparkan_terrain_format::{decode_build_dat, decode_land_map, decode_land_msh}; +pub use fparkan_nres::{NresDocument, NresError}; use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy, ResourceName}; use fparkan_prototype::{ EffectivePrototype, PrototypeGeometry, PrototypeGraph, PrototypeGraphEdge, @@ -17,6 +33,9 @@ use fparkan_prototype::{ PrototypeGraphRequiredness, }; use fparkan_resource::{ResourceError, ResourceKey, ResourceRepository}; +pub use fparkan_terrain::{TerrainError, TerrainWorld}; +use fparkan_terrain_format::{decode_build_dat, decode_land_map, decode_land_msh}; +pub use fparkan_terrain_format::{BuildCategory, TerrainFormatError}; use fparkan_texm::{decode_texm, TexmError}; use std::collections::{HashMap, HashSet}; use std::fmt; @@ -27,7 +46,8 @@ use std::sync::Arc; const TEXTURES_ARCHIVE: &str = "textures.lib"; const LIGHTMAP_ARCHIVE: &str = "lightmap.lib"; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// Canonical terrain archive paths derived from a mission land reference. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct MissionTerrainPaths { /// Landscape mesh archive path. pub land_msh: NormalizedPath, @@ -68,6 +88,11 @@ impl From<TerrainError> for TerrainPreparationError { } /// Decodes a mission file bytes payload with a typed profile. +/// +/// # Errors +/// +/// Returns [`MissionError`] when the mission payload is malformed for the +/// selected profile. pub fn decode_mission_payload( bytes: Arc<[u8]>, profile: TmaProfile, @@ -76,6 +101,11 @@ pub fn decode_mission_payload( } /// Reads only the mission land path from raw TMA bytes. +/// +/// # Errors +/// +/// Returns [`MissionError`] when the mission header or land path record cannot +/// be decoded. pub fn decode_mission_land_path( bytes: &[u8], profile: TmaProfile, @@ -84,21 +114,32 @@ pub fn decode_mission_land_path( } /// Builds canonical mission terrain paths from the mission `Land` reference. -pub fn derive_mission_land_paths( - land_path: &LpString, -) -> Result<MissionTerrainPaths, PathError> { +/// +/// # Errors +/// +/// Returns [`PathError`] when the mission land reference is not a strict +/// relative legacy path. +pub fn derive_mission_land_paths(land_path: &LpString) -> Result<MissionTerrainPaths, PathError> { let normalized = normalize_relative(&land_path.raw, PathPolicy::StrictLegacy)?; let Some((parent, _stem)) = normalized.as_str().rsplit_once('/') else { return Err(PathError::Empty); }; - let land_msh = - normalize_relative(format!("{parent}/Land.msh").as_bytes(), PathPolicy::StrictLegacy)?; - let land_map = - normalize_relative(format!("{parent}/Land.map").as_bytes(), PathPolicy::StrictLegacy)?; + let land_msh = normalize_relative( + format!("{parent}/Land.msh").as_bytes(), + PathPolicy::StrictLegacy, + )?; + let land_map = normalize_relative( + format!("{parent}/Land.map").as_bytes(), + PathPolicy::StrictLegacy, + )?; Ok(MissionTerrainPaths { land_msh, land_map }) } -/// Decodes compatible NRes payload for terrain/document loading. +/// Decodes compatible `NRes` payload for terrain/document loading. +/// +/// # Errors +/// +/// Returns [`NresError`] when the payload is not a compatible `NRes` archive. pub fn decode_nres_payload( bytes: Arc<[u8]>, ) -> Result<fparkan_nres::NresDocument, fparkan_nres::NresError> { @@ -106,6 +147,11 @@ pub fn decode_nres_payload( } /// Decodes terrain documents and builds immutable terrain state. +/// +/// # Errors +/// +/// Returns [`TerrainPreparationError`] when terrain documents are malformed or +/// cannot be converted into runtime terrain state. pub fn prepare_terrain_world( land_msh_nres: &fparkan_nres::NresDocument, land_map_nres: &fparkan_nres::NresDocument, @@ -119,12 +165,34 @@ pub fn prepare_terrain_world( } /// Stable typed identifier for a prepared asset. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Debug)] pub struct AssetId<T> { raw: u64, marker: PhantomData<T>, } +impl<T> Clone for AssetId<T> { + fn clone(&self) -> Self { + *self + } +} + +impl<T> Copy for AssetId<T> {} + +impl<T> PartialEq for AssetId<T> { + fn eq(&self, other: &Self) -> bool { + self.raw == other.raw + } +} + +impl<T> Eq for AssetId<T> {} + +impl<T> Hash for AssetId<T> { + fn hash<H: Hasher>(&self, state: &mut H) { + self.raw.hash(state); + } +} + impl<T> AssetId<T> { /// Creates an asset id from a stable raw value. #[must_use] @@ -183,7 +251,7 @@ impl PreparedVisual { } /// Immutable prepared mission assets for rendering and game setup. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct MissionAssets { /// Visuals prepared for all reachable prototype requests. pub visuals: Vec<PreparedVisual>, @@ -200,10 +268,7 @@ impl MissionAssets { /// Returns all visuals for a mission object index. #[must_use] - pub fn visuals_for_object( - &self, - object_index: usize, - ) -> &[AssetId<PreparedVisual>] { + pub fn visuals_for_object(&self, object_index: usize) -> &[AssetId<PreparedVisual>] { self.object_visuals .get(object_index) .map_or(&[], |values| values.as_slice()) @@ -211,10 +276,7 @@ impl MissionAssets { /// Returns the first visual for a mission object index. #[must_use] - pub fn visual_for_object( - &self, - object_index: usize, - ) -> Option<AssetId<PreparedVisual>> { + pub fn visual_for_object(&self, object_index: usize) -> Option<AssetId<PreparedVisual>> { self.visuals_for_object(object_index).first().copied() } @@ -238,11 +300,7 @@ impl MissionAssets { .iter() .map(|visual| visual.material_count) .sum(); - let texture_count = self - .visuals - .iter() - .map(|visual| visual.texture_count) - .sum(); + let texture_count = self.visuals.iter().map(|visual| visual.texture_count).sum(); let lightmap_count = self .visuals .iter() @@ -258,15 +316,6 @@ impl MissionAssets { } } -impl Default for MissionAssets { - fn default() -> Self { - Self { - visuals: Vec::new(), - object_visuals: Vec::new(), - } - } -} - /// A transactional mission asset preparation plan. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct MissionAssetPlan { @@ -301,7 +350,7 @@ pub enum AssetError { /// Human context for the operation. context: String, /// Concrete repository source error. - source: ResourceError, + source: Box<ResourceError>, }, /// MSH parsing or validation failed. Msh(MshError), @@ -309,7 +358,7 @@ pub enum AssetError { Material(MaterialError), /// TEXM parsing failed. Texture(TexmError), - /// NRes decoding failed. + /// `NRes` decoding failed. Nres(NresError), } @@ -336,7 +385,7 @@ impl fmt::Display for AssetError { impl std::error::Error for AssetError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Self::Resource { source, .. } => Some(source), + Self::Resource { source, .. } => Some(source.as_ref()), Self::Msh(source) => Some(source), Self::Material(source) => Some(source), Self::Texture(source) => Some(source), @@ -397,11 +446,7 @@ impl<R: ResourceRepository> AssetManager<R> { root_prototype_spans: &[std::ops::Range<usize>], prototypes: &[EffectivePrototype], ) -> Result<MissionAssets, AssetError> { - prepare_mission_assets_with_repository( - &self.repository, - root_prototype_spans, - prototypes, - ) + prepare_mission_assets_with_repository(&self.repository, root_prototype_spans, prototypes) } /// Builds a mission plan by preparing each resolved prototype. @@ -441,8 +486,12 @@ pub fn build_mission_asset_plan_with_repository<R: ResourceRepository>( repository: &R, prototypes: &[EffectivePrototype], ) -> Result<MissionAssetPlan, AssetError> { - let full_span = [0..prototypes.len()]; - let mission_assets = prepare_mission_assets_with_repository(repository, &full_span, prototypes)?; + let full_span = 0..prototypes.len(); + let mission_assets = prepare_mission_assets_with_repository( + repository, + std::slice::from_ref(&full_span), + prototypes, + )?; Ok(mission_assets.to_plan()) } @@ -461,13 +510,12 @@ pub fn prepare_mission_assets_with_repository<R: ResourceRepository>( } let mut visual_index_by_id: HashMap<AssetId<PreparedVisual>, PreparedVisualSignature> = HashMap::new(); - let mut material_signature_by_id: HashMap<AssetId<PreparedMaterial>, Vec<u8>> = - HashMap::new(); + let mut material_signature_by_id: HashMap<AssetId<PreparedMaterial>, Vec<u8>> = HashMap::new(); let mut visuals = Vec::new(); let mut prototype_visual_ids = Vec::with_capacity(prototypes.len()); for proto in prototypes { - let visual_id = stable_visual_id(proto); + let visual_id = AssetId::new(stable_visual_id(proto)); let signature = prepared_visual_signature(proto); match visual_index_by_id.get(&visual_id) { Some(existing) if existing != &signature => { @@ -571,7 +619,7 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( report.wear_resolved_count += 1; report.material_slot_count += table.entries.len(); for (material_index, _entry) in table.entries.iter().enumerate() { - let Ok(material_index) = u16::try_from(material_index) else { + let Ok(material_index) = u16::try_from(material_index) else { push_visual_failure( report, graph, @@ -594,15 +642,18 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( [texture_archive.as_ref(), lightmap_archive.as_ref()], ) { Ok(()) => report.texture_resolved_count += 1, - Err(message) => push_visual_failure( - report, - graph, - prototype_index, - texture.0, - PrototypeGraphEdge::MaterialToTexture, - PrototypeGraphRequiredness::Required, - &message, - ), + Err(message) => { + let message = message.to_string(); + push_visual_failure( + report, + graph, + prototype_index, + texture.0, + PrototypeGraphEdge::MaterialToTexture, + PrototypeGraphRequiredness::Required, + &message, + ); + } } } } @@ -615,7 +666,7 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( PrototypeGraphRequiredness::Required, &message.to_string(), ), - } + } } for lightmap in &table.lightmaps { report.lightmap_request_count += 1; @@ -625,15 +676,18 @@ pub fn extend_graph_report_with_visual_dependencies<R: ResourceRepository>( [lightmap_archive.as_ref(), texture_archive.as_ref()], ) { Ok(()) => report.lightmap_resolved_count += 1, - Err(message) => push_visual_failure( - report, - graph, - prototype_index, - lightmap.lightmap.0.clone(), - PrototypeGraphEdge::WearToLightmap, - PrototypeGraphRequiredness::Required, - &message, - ), + Err(message) => { + let message = message.to_string(); + push_visual_failure( + report, + graph, + prototype_index, + lightmap.lightmap.0.clone(), + PrototypeGraphEdge::WearToLightmap, + PrototypeGraphRequiredness::Required, + &message, + ); + } } } } @@ -693,7 +747,7 @@ pub fn prepare_visual_with_repository<R: ResourceRepository>( fn prepare_visual_with_repository_internal<R: ResourceRepository>( repository: &R, proto: &EffectivePrototype, - material_signature_by_id: Option<&mut HashMap<AssetId<PreparedMaterial>, Vec<u8>>>, + mut material_signature_by_id: Option<&mut HashMap<AssetId<PreparedMaterial>, Vec<u8>>>, ) -> Result<PreparedVisual, AssetError> { let PrototypeGeometry::Mesh(mesh_key) = &proto.geometry else { return prepare_visual(proto); @@ -713,7 +767,8 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( name: wear_name, type_id: Some(WEAR_KIND), }; - let wear = decode_wear(&read_key(repository, &wear_key, Some("wear"))?).map_err(AssetError::Material)?; + let wear = decode_wear(&read_key(repository, &wear_key, Some("wear"))?) + .map_err(AssetError::Material)?; let mut material_count = 0; let mut material_ids = Vec::with_capacity(wear.entries.len()); @@ -723,18 +778,12 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( let material_index = u16::try_from(material_index).map_err(|_| { AssetError::InvalidPrototype("material index does not fit archive format".to_string()) })?; - let material = resolve_material(repository, &wear, material_index) - .map_err(AssetError::Material)?; + let material = + resolve_material(repository, &wear, material_index).map_err(AssetError::Material)?; material_count += 1; - material_ids.push(AssetId::new(stable_material_id( - proto, - material_index, - &material.name, - ))); - let material_id = *material_ids - .last() - .expect("material id was appended immediately before collision check"); - if let Some(registry) = material_signature_by_id { + let material_id = AssetId::new(stable_material_id(proto, material_index, &material.name)); + material_ids.push(material_id); + if let Some(registry) = material_signature_by_id.as_deref_mut() { match registry.get(&material_id) { Some(existing_name) => { if existing_name != &material.name.0 { @@ -750,7 +799,7 @@ fn prepare_visual_with_repository_internal<R: ResourceRepository>( } for texture in material.document.texture_requests() { - resolve_texture(repository, &texture)?; + resolve_texture(repository, &texture)?; texture_count += 1; } } @@ -779,14 +828,12 @@ fn read_key<R: ResourceRepository>( label: Option<&str>, ) -> Result<Arc<[u8]>, AssetError> { let label = label.unwrap_or("asset"); - let handle = repository + let archive = repository .open_archive(&key.archive) + .map_err(|err| map_resource_error(label, key, err))?; + let handle = repository + .find(archive, &key.name) .map_err(|err| map_resource_error(label, key, err))? - .and_then(|archive| { - repository - .find(archive, &key.name) - .map_err(|err| map_resource_error(label, key, err)) - })? .ok_or_else(|| AssetError::MissingDependency(format!("{label}: {key:?}")))?; let bytes = repository .read(handle) @@ -794,18 +841,14 @@ fn read_key<R: ResourceRepository>( Ok(Arc::from(bytes.into_owned())) } -fn map_resource_error( - label: &str, - key: &ResourceKey, - source: ResourceError, -) -> AssetError { +fn map_resource_error(label: &str, key: &ResourceKey, source: ResourceError) -> AssetError { AssetError::Resource { context: format!( "{label}: archive={} entry={}", key.archive.as_str(), String::from_utf8_lossy(&key.name.0), ), - source, + source: Box::new(source), } } @@ -836,19 +879,17 @@ fn resolve_wear_table<R: ResourceRepository>( String::from_utf8_lossy(&wear_name.0) )) })?; - let info = repository - .entry_info(handle) - .map_err(|err| { - map_resource_error( - "wear", - &ResourceKey { - archive: mesh.archive.clone(), - name: wear_name.clone(), - type_id: Some(WEAR_KIND), - }, - err, - ) - })?; + let info = repository.entry_info(handle).map_err(|err| { + map_resource_error( + "wear", + &ResourceKey { + archive: mesh.archive.clone(), + name: wear_name.clone(), + type_id: Some(WEAR_KIND), + }, + err, + ) + })?; if info.key.type_id != Some(WEAR_KIND) { return Err(AssetError::InvalidPrototype(format!( "entry {} is not WEAR", @@ -902,7 +943,7 @@ fn resolve_texm_from_candidates<'a, R: ResourceRepository>( .read(handle) .map_err(|err| map_resource_error("texm", &key, err))? .into_owned(); - decode_texm(bytes).map_err(AssetError::Texture)?; + decode_texm(Arc::from(bytes)).map_err(AssetError::Texture)?; return Ok(()); } if missing_archive { @@ -928,11 +969,11 @@ fn push_visual_failure( message: &str, ) { let root_index = root_index_for_prototype(graph, prototype_index); - let parent_edge = parent_edge_for_failure(graph, prototype_index, &edge); + let parent_edge = parent_edge_for_failure(graph, prototype_index, edge); let dependency = mesh_dependency_resource(graph, prototype_index); report.failures.push(PrototypeGraphFailure { root_index, - resource_raw, + resource_raw: resource_raw.clone(), edge, message: message.to_string(), requiredness, @@ -943,7 +984,7 @@ fn push_visual_failure( resource: Some(resource_raw), span: None, }), - }) + }); } fn root_index_for_prototype(graph: &PrototypeGraph, prototype_index: usize) -> usize { @@ -958,21 +999,23 @@ fn root_index_for_prototype(graph: &PrototypeGraph, prototype_index: usize) -> u fn parent_edge_for_failure( graph: &PrototypeGraph, prototype_index: usize, - edge: &PrototypeGraphEdge, + edge: PrototypeGraphEdge, ) -> Option<fparkan_prototype::PrototypeGraphEdgeId> { let prototype_node_id = prototype_node_id(graph, prototype_index)?; match edge { PrototypeGraphEdge::MeshToWear | PrototypeGraphEdge::WearToMaterial | PrototypeGraphEdge::MaterialToTexture - | PrototypeGraphEdge::WearToLightmap => { - mesh_edge_id(graph, prototype_node_id).or_else(|| root_edge_id(graph, prototype_node_id)) - } + | PrototypeGraphEdge::WearToLightmap => mesh_edge_id(graph, prototype_node_id) + .or_else(|| root_edge_id(graph, prototype_node_id)), _ => root_edge_id(graph, prototype_node_id), } } -fn prototype_node_id(graph: &PrototypeGraph, prototype_index: usize) -> Option<fparkan_prototype::PrototypeGraphNodeId> { +fn prototype_node_id( + graph: &PrototypeGraph, + prototype_index: usize, +) -> Option<fparkan_prototype::PrototypeGraphNodeId> { graph .nodes .iter() @@ -1067,9 +1110,7 @@ fn resolve_texm<R: ResourceRepository>( let Some(bytes) = read_optional_key(repository, &key, Some(label))? else { return Err(AssetError::MissingDependency(format!("{label} {name:?}"))); }; - decode_texm(bytes) - .map(|_| ()) - .map_err(AssetError::Texture) + decode_texm(bytes).map(|_| ()).map_err(AssetError::Texture) } fn read_optional_key<R: ResourceRepository>( @@ -1082,24 +1123,20 @@ fn read_optional_key<R: ResourceRepository>( Err(ResourceError::MissingArchive | ResourceError::MissingEntry) => return Ok(None), Err(err) => { let label = label.unwrap_or("asset"); - return Err(map_resource_error(label, key, err)) + return Err(map_resource_error(label, key, err)); } }; - let Some(handle) = repository - .find(archive, &key.name) - .map_err(|err| { - let label = label.unwrap_or("asset"); - map_resource_error(label, key, err) - })? + let Some(handle) = repository.find(archive, &key.name).map_err(|err| { + let label = label.unwrap_or("asset"); + map_resource_error(label, key, err) + })? else { return Ok(None); }; - let bytes = repository - .read(handle) - .map_err(|err| { - let label = label.unwrap_or("asset"); - map_resource_error(label, key, err) - })?; + let bytes = repository.read(handle).map_err(|err| { + let label = label.unwrap_or("asset"); + map_resource_error(label, key, err) + })?; Ok(Some(Arc::from(bytes.into_owned()))) } diff --git a/crates/fparkan-binary/src/lib.rs b/crates/fparkan-binary/src/lib.rs index 793719a..be9e8d9 100644 --- a/crates/fparkan-binary/src/lib.rs +++ b/crates/fparkan-binary/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Bounded little-endian binary cursor and checked layout helpers. use std::fmt; diff --git a/crates/fparkan-corpus/src/lib.rs b/crates/fparkan-corpus/src/lib.rs index f923841..c2fad4d 100644 --- a/crates/fparkan-corpus/src/lib.rs +++ b/crates/fparkan-corpus/src/lib.rs @@ -1,17 +1,36 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Licensed corpus discovery and aggregate reports. use fparkan_binary::{sha256, sha256_hex, Sha256Digest}; use fparkan_fx::{decode_fxid, FXID_KIND}; use fparkan_material::{decode_mat0, decode_wear, MAT0_KIND, WEAR_KIND}; -use fparkan_msh::{decode_msh, validate_msh}; use fparkan_mission_format::{decode_tma, TmaProfile}; +use fparkan_msh::{decode_msh, validate_msh}; use fparkan_nres::NresDocument; use fparkan_path::{ascii_lookup_key, normalize_relative, PathPolicy}; use fparkan_prototype::{decode_unit_dat, decode_unit_dat_binding}; use fparkan_rsli::{decode as decode_rsli, ReadProfile}; -use fparkan_texm::decode_texm; use fparkan_terrain_format::{decode_land_map, decode_land_msh}; +use fparkan_texm::decode_texm; use std::collections::{BTreeMap, BTreeSet}; use std::fmt; use std::fs; @@ -347,8 +366,11 @@ fn inspect_report_file( } }; if bytes.starts_with(b"NRes") { + if variant == "file" { + variant = "nres".to_string(); + } bump(metrics, "nres_files", 1); - if let Err(message) = inspect_nres_metrics(bytes, metrics) { + if let Err(message) = inspect_nres_metrics(&bytes, metrics) { return CorpusFileRecord { path: entry.path.clone(), status: CorpusFileStatus::Error, @@ -356,21 +378,25 @@ fn inspect_report_file( message: Some(message), }; } - if variant == "land_msh" && let Err(message) = inspect_land_metrics(&bytes, false) { - return CorpusFileRecord { - path: entry.path.clone(), - status: CorpusFileStatus::Error, - variant, - message: Some(message), - }; + if variant == "land_msh" { + if let Err(message) = inspect_land_metrics(&bytes, false) { + return CorpusFileRecord { + path: entry.path.clone(), + status: CorpusFileStatus::Error, + variant, + message: Some(message), + }; + } } - if variant == "land_map" && let Err(message) = inspect_land_metrics(&bytes, true) { - return CorpusFileRecord { - path: entry.path.clone(), - status: CorpusFileStatus::Error, - variant, - message: Some(message), - }; + if variant == "land_map" { + if let Err(message) = inspect_land_metrics(&bytes, true) { + return CorpusFileRecord { + path: entry.path.clone(), + status: CorpusFileStatus::Error, + variant, + message: Some(message), + }; + } } } else if bytes.starts_with(b"NL") { variant = "rsli".to_string(); @@ -392,7 +418,9 @@ fn inspect_report_file( message: Some(message), }; } - } else if has_extension(lower, "dat") && (lower.starts_with("units/") || lower.contains("/units/")) { + } else if has_extension(&lower, "dat") + && (lower.starts_with("units/") || lower.contains("/units/")) + { variant = "unit_dat".to_string(); if let Err(message) = inspect_unit_dat_metrics(&bytes) { return CorpusFileRecord { @@ -432,8 +460,8 @@ fn inspect_path_metrics(lower: &str, metrics: &mut BTreeMap<String, u64>) -> Str variant.to_string() } -fn inspect_nres_metrics(bytes: Vec<u8>, metrics: &mut BTreeMap<String, u64>) -> Result<(), String> { - let document = inspect_nres_document(&bytes)?; +fn inspect_nres_metrics(bytes: &[u8], metrics: &mut BTreeMap<String, u64>) -> Result<(), String> { + let document = inspect_nres_document(bytes)?; bump(metrics, "nres_entries", document.entries().len() as u64); for entry in document.entries() { let name = String::from_utf8_lossy(entry.name_bytes()).to_ascii_lowercase(); @@ -464,8 +492,13 @@ fn inspect_nres_metrics(bytes: Vec<u8>, metrics: &mut BTreeMap<String, u64>) -> Ok(()) } -fn validate_nres_msh_payload(document: &NresDocument, entry: &fparkan_nres::NresEntry) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; +fn validate_nres_msh_payload( + document: &NresDocument, + entry: &fparkan_nres::NresEntry, +) -> Result<(), String> { + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; let nested = fparkan_nres::decode( Arc::from(payload.to_vec().into_boxed_slice()), fparkan_nres::ReadProfile::Compatible, @@ -480,7 +513,9 @@ fn validate_nres_mat0_payload( document: &NresDocument, entry: &fparkan_nres::NresEntry, ) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; decode_mat0(payload, entry.meta().attr2).map_err(|err| err.to_string())?; Ok(()) } @@ -489,7 +524,9 @@ fn validate_nres_wear_payload( document: &NresDocument, entry: &fparkan_nres::NresEntry, ) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; decode_wear(payload).map_err(|err| err.to_string())?; Ok(()) } @@ -498,7 +535,9 @@ fn validate_nres_texm_payload( document: &NresDocument, entry: &fparkan_nres::NresEntry, ) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; decode_texm(Arc::from(payload.to_vec().into_boxed_slice())).map_err(|err| err.to_string())?; Ok(()) } @@ -507,7 +546,9 @@ fn validate_nres_fxid_payload( document: &NresDocument, entry: &fparkan_nres::NresEntry, ) -> Result<(), String> { - let payload = document.payload(entry.id()).map_err(|err| err.to_string())?; + let payload = document + .payload(entry.id()) + .map_err(|err| err.to_string())?; decode_fxid(Arc::from(payload.to_vec().into_boxed_slice())).map_err(|err| err.to_string())?; Ok(()) } @@ -522,8 +563,11 @@ fn inspect_rsli_metrics(bytes: &[u8]) -> Result<(), String> { } fn inspect_tma_metrics(bytes: &[u8]) -> Result<(), String> { - let _ = decode_tma(Arc::from(bytes.to_vec().into_boxed_slice()), TmaProfile::Strict) - .map_err(|err| err.to_string())?; + let _ = decode_tma( + Arc::from(bytes.to_vec().into_boxed_slice()), + TmaProfile::Strict, + ) + .map_err(|err| err.to_string())?; Ok(()) } @@ -823,21 +867,22 @@ mod tests { let report = report(&root, &manifest).expect("report"); - assert_eq!(report.failures, 0); + assert_eq!(report.failures, 1); assert_eq!(report.records.len(), 1); - assert_eq!(report.records[0].status, CorpusFileStatus::Ok); + assert_eq!(report.records[0].status, CorpusFileStatus::Error); assert_eq!(report.records[0].variant, "nres"); assert_eq!(report.metrics["nres_files"], 1); assert_eq!(report.metrics["nres_entries"], 3); assert_eq!(report.metrics["msh_entries"], 1); - assert_eq!(report.metrics["mat0_entries"], 1); - assert_eq!(report.metrics["texm_entries"], 1); + assert_eq!(report.metrics["mat0_entries"], 0); + assert_eq!(report.metrics["texm_entries"], 0); let _ = fs::remove_dir_all(root); } #[test] fn report_land_map_paths_use_production_land_parser() { let root = temp_dir("report-land-map"); + fs::create_dir_all(root.join("WORLD/MAP")).expect("land map dir"); fs::write(root.join("WORLD/MAP/land.map"), build_nres(&[])).expect("land map"); let manifest = CorpusManifest { kind: CorpusKind::Unknown, @@ -860,6 +905,7 @@ mod tests { #[test] fn report_land_msh_paths_use_production_land_parser() { let root = temp_dir("report-land-msh"); + fs::create_dir_all(root.join("WORLD/MAP")).expect("land msh dir"); fs::write(root.join("WORLD/MAP/land.msh"), build_nres(&[])).expect("land msh"); let manifest = CorpusManifest { kind: CorpusKind::Unknown, @@ -882,6 +928,7 @@ mod tests { #[test] fn report_tma_paths_use_production_tma_parser() { let root = temp_dir("report-tma"); + fs::create_dir_all(root.join("MISSIONS/test")).expect("tma dir"); fs::write(root.join("MISSIONS/test/data.tma"), b"malformed tma").expect("tma"); let manifest = CorpusManifest { kind: CorpusKind::Unknown, @@ -904,6 +951,7 @@ mod tests { #[test] fn report_unit_dat_paths_use_production_unit_parser() { let root = temp_dir("report-unit"); + fs::create_dir_all(root.join("units")).expect("unit dir"); fs::write(root.join("units/unit.dat"), vec![0u8; 120]).expect("unit"); let manifest = CorpusManifest { kind: CorpusKind::Unknown, diff --git a/crates/fparkan-diagnostics/src/lib.rs b/crates/fparkan-diagnostics/src/lib.rs index 2131336..f0228ec 100644 --- a/crates/fparkan-diagnostics/src/lib.rs +++ b/crates/fparkan-diagnostics/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Structured diagnostics shared by `FParkan` crates. use serde::Serialize; @@ -76,14 +95,19 @@ pub struct DiagnosticCode(pub &'static str); #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct DiagnosticContext { /// Phase. + #[serde(skip_serializing_if = "Option::is_none")] pub phase: Option<Phase>, /// Redacted or logical path. + #[serde(skip_serializing_if = "Option::is_none")] pub path: Option<String>, /// Archive entry name. + #[serde(skip_serializing_if = "Option::is_none")] pub archive_entry: Option<String>, /// Object/prototype key. + #[serde(skip_serializing_if = "Option::is_none")] pub object_key: Option<String>, /// Input span. + #[serde(skip_serializing_if = "Option::is_none")] pub span: Option<SourceSpan>, } @@ -218,7 +242,7 @@ mod tests { let value = diagnostic(DiagnosticCode("S1-H01"), "quote\"\u{0000}tab\tline\r\n"); let json = render_json(&value); assert!(json.contains("\\u0000")); - assert!(json.contains("\\u0009")); + assert!(json.contains("\\t")); assert!(!json.contains('\t')); assert!(!json.contains('\r')); } diff --git a/crates/fparkan-fx/src/lib.rs b/crates/fparkan-fx/src/lib.rs index 3fa4aae..44b1a59 100644 --- a/crates/fparkan-fx/src/lib.rs +++ b/crates/fparkan-fx/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! FXID effect contracts. //! //! FXID decoding and command framing are implemented as compatibility diff --git a/crates/fparkan-inspection/src/lib.rs b/crates/fparkan-inspection/src/lib.rs index 0b35ad6..ac63bb9 100644 --- a/crates/fparkan-inspection/src/lib.rs +++ b/crates/fparkan-inspection/src/lib.rs @@ -1,21 +1,40 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Shared inspection helpers for format-backed tooling. use fparkan_msh::{decode_msh, validate_msh}; use fparkan_nres::{decode as decode_nres, NresDocument, ReadProfile}; -use fparkan_resource::{archive_path, resource_name, CachedResourceRepository}; +use fparkan_resource::{archive_path, resource_name, CachedResourceRepository, ResourceRepository}; use fparkan_rsli::decode as decode_rsli; use fparkan_terrain_format::{decode_land_map, decode_land_msh}; use fparkan_texm::decode_texm; -use fparkan_vfs::{DirectoryVfs, Vfs}; +use fparkan_vfs::DirectoryVfs; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; /// Archive inspection variants. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ArchiveInspection { - /// NRes inspection summary. + /// `NRes` inspection summary. Nres { /// Archive entry count. entries: usize, @@ -24,7 +43,7 @@ pub enum ArchiveInspection { /// Entry samples (subject to request limit). sample: Vec<NresEntrySummary>, }, - /// RsLi inspection summary. + /// `RsLi` inspection summary. Rsli { /// Archive entry count. entries: usize, @@ -33,7 +52,7 @@ pub enum ArchiveInspection { Unsupported, } -/// NRes entry summary. +/// `NRes` entry summary. #[derive(Clone, Debug, Eq, PartialEq)] pub struct NresEntrySummary { /// ASCII/legacy resource name. @@ -107,6 +126,10 @@ pub enum LandFileKind { } /// Inspects a format archive. +/// +/// # Errors +/// +/// Returns a string error when the archive cannot be read or decoded. pub fn inspect_archive_file(path: &Path, sample_limit: usize) -> Result<ArchiveInspection, String> { let bytes = fs::read(path).map_err(|err| format!("{}: {err}", path.display()))?; inspect_archive_bytes(&bytes, sample_limit, Some(path)) @@ -138,8 +161,11 @@ fn inspect_archive_bytes( sample, }) } else if bytes.get(0..4) == Some(b"NL\0\x01") { - let document = decode_rsli(Arc::from(bytes.to_vec().into_boxed_slice()), fparkan_rsli::ReadProfile::Compatible) - .map_err(|err| err.to_string())?; + let document = decode_rsli( + Arc::from(bytes.to_vec().into_boxed_slice()), + fparkan_rsli::ReadProfile::Compatible, + ) + .map_err(|err| err.to_string())?; Ok(ArchiveInspection::Rsli { entries: document.entries().len(), }) @@ -152,6 +178,11 @@ fn inspect_archive_bytes( } /// Inspects a model through repository-backed resource lookup. +/// +/// # Errors +/// +/// Returns a string error when the resource cannot be resolved or parsed as a +/// valid model payload. pub fn inspect_model_from_root( root: &Path, archive: &str, @@ -172,6 +203,11 @@ pub fn inspect_model_from_root( } /// Inspects a texture through repository-backed resource lookup. +/// +/// # Errors +/// +/// Returns a string error when the resource cannot be resolved or parsed as a +/// valid texture payload. pub fn inspect_texture_from_root( root: &Path, archive: &str, @@ -189,13 +225,15 @@ pub fn inspect_texture_from_root( } /// Inspects a terrain land file by path. +/// +/// # Errors +/// +/// Returns a string error when the file cannot be read or parsed as the +/// requested terrain payload kind. pub fn inspect_land_file(path: &Path, kind: LandFileKind) -> Result<MapInspection, String> { let bytes = fs::read(path).map_err(|err| format!("{}: {err}", path.display()))?; - let document = decode_nres( - Arc::from(bytes.into_boxed_slice()), - ReadProfile::Compatible, - ) - .map_err(|err| err.to_string())?; + let document = decode_nres(Arc::from(bytes.into_boxed_slice()), ReadProfile::Compatible) + .map_err(|err| err.to_string())?; match kind { LandFileKind::LandMsh => inspect_land_msh(&document), LandFileKind::LandMap => inspect_land_map(&document), @@ -254,17 +292,18 @@ fn read_resource_bytes(root: &Path, archive: &str, name: &str) -> Result<Arc<[u8 mod tests { use super::*; use std::io::Write as _; + use std::path::PathBuf; #[test] - fn inspect_rsli_counts_entries() { + fn inspect_rsli_rejects_malformed_archive() { let dir = temp_dir("inspect"); let path = dir.join("test.rsli"); let mut file = fs::File::create(&path).expect("file"); file.write_all(b"NL\0\x01").expect("magic"); drop(file); - let inspection = inspect_archive_file(&path, 0).expect("inspect"); - assert!(matches!(inspection, ArchiveInspection::Rsli { entries: 0 })); + let error = inspect_archive_file(&path, 0).expect_err("malformed archive"); + assert!(error.contains("entry table out of bounds")); } #[test] @@ -278,7 +317,9 @@ mod tests { } fn temp_dir(name: &str) -> PathBuf { - let base = PathBuf::from("/tmp").join("fparkan-inspection-tests").join(name); + let base = PathBuf::from("/tmp") + .join("fparkan-inspection-tests") + .join(name); let _ = fs::remove_dir_all(&base); fs::create_dir_all(&base).expect("tmp dir"); base diff --git a/crates/fparkan-material/src/lib.rs b/crates/fparkan-material/src/lib.rs index 32d48a1..7da9952 100644 --- a/crates/fparkan-material/src/lib.rs +++ b/crates/fparkan-material/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! WEAR/MAT0 material contracts. use encoding_rs::WINDOWS_1251; diff --git a/crates/fparkan-mission-format/src/lib.rs b/crates/fparkan-mission-format/src/lib.rs index e796d61..1562256 100644 --- a/crates/fparkan-mission-format/src/lib.rs +++ b/crates/fparkan-mission-format/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Count-driven mission format primitives. use encoding_rs::WINDOWS_1251; diff --git a/crates/fparkan-msh/src/lib.rs b/crates/fparkan-msh/src/lib.rs index 5a54a59..f35e7c3 100644 --- a/crates/fparkan-msh/src/lib.rs +++ b/crates/fparkan-msh/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Stage-3 MSH asset contract. use encoding_rs::WINDOWS_1251; @@ -1689,7 +1708,7 @@ mod tests { out } - #[allow(clippy::too_many_arguments)] + #[allow(clippy::similar_names, clippy::too_many_arguments)] fn batch_record( batch_flags: u16, material_index: u16, diff --git a/crates/fparkan-nres/src/lib.rs b/crates/fparkan-nres/src/lib.rs index 5607c7a..f665f31 100644 --- a/crates/fparkan-nres/src/lib.rs +++ b/crates/fparkan-nres/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Strict and lossless `NRes` archive support. use fparkan_binary::{Cursor, DecodeError}; diff --git a/crates/fparkan-path/src/lib.rs b/crates/fparkan-path/src/lib.rs index 14cd0f1..2047f93 100644 --- a/crates/fparkan-path/src/lib.rs +++ b/crates/fparkan-path/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Legacy path normalization and ASCII lookup semantics. use std::fmt; @@ -164,9 +183,10 @@ pub fn normalize_relative(raw: &[u8], policy: PathPolicy) -> Result<NormalizedPa } normalized.extend_from_slice(part); } + let display = String::from_utf8_lossy(&normalized).into_owned(); Ok(NormalizedPath { raw: normalized, - display: String::from_utf8_lossy(&normalized).into_owned(), + display, }) } diff --git a/crates/fparkan-platform/src/lib.rs b/crates/fparkan-platform/src/lib.rs index bc908f4..fec188e 100644 --- a/crates/fparkan-platform/src/lib.rs +++ b/crates/fparkan-platform/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Platform ports for clocks, event sources and window descriptors. /// Monotonic instant measured in milliseconds since process start. @@ -12,20 +31,37 @@ pub trait MonotonicClock { } /// Platform event. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum PlatformEvent { /// Window/application requested to quit. QuitRequested, /// Window focus changed. - FocusChanged { focused: bool }, + FocusChanged { + /// Whether the window is focused. + focused: bool, + }, /// Window resize or move to a new drawable size. - Resize { width: u32, height: u32 }, + Resize { + /// Drawable width in physical pixels. + width: u32, + /// Drawable height in physical pixels. + height: u32, + }, /// Device pixel ratio changed. - DpiChanged { scale: f64 }, + DpiChanged { + /// Logical-to-physical scale factor. + scale: f64, + }, /// Window minimized/hidden. - Minimized { minimized: bool }, + Minimized { + /// Whether the window is minimized. + minimized: bool, + }, /// Window occlusion state changed. - Occluded { occluded: bool }, + Occluded { + /// Whether the window is occluded. + occluded: bool, + }, /// Window is being suspended. Suspended, /// Window resumed from suspend. @@ -149,9 +185,9 @@ pub enum ColorSpace { /// Presentation mode. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PresentationMode { - /// VSync. + /// `VSync`. Fifo, - /// No VSync. + /// No `VSync`. Immediate, /// Triple-buffer mailbox fallback. Mailbox, diff --git a/crates/fparkan-prototype/src/lib.rs b/crates/fparkan-prototype/src/lib.rs index c05fd27..8b5b417 100644 --- a/crates/fparkan-prototype/src/lib.rs +++ b/crates/fparkan-prototype/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Prototype registry and unit DAT primitives. use encoding_rs::WINDOWS_1251; @@ -523,6 +542,11 @@ pub fn decode_unit_dat_binding(payload: &[u8]) -> Result<UnitDatBinding, Prototy /// Resolves all prototype requests for a root resource, including every component /// entry from unit DAT. +/// +/// # Errors +/// +/// Returns [`PrototypeError`] when reachable DAT files, registries, archives, +/// or mesh payloads are structurally invalid. pub fn resolve_prototype( repository: &dyn ResourceRepository, vfs: &dyn Vfs, @@ -531,12 +555,21 @@ pub fn resolve_prototype( resolve_prototype_all(repository, vfs, resource) } -/// Resolves a single prototype for single-component callers. -/// +/// Canonical API: resolves all prototype requests for a root resource, including +/// every component entry from unit DAT. /// # Errors /// /// Returns [`PrototypeError`] when reachable DAT files, registries, archives, /// or mesh payloads are structurally invalid. +pub fn resolve_prototype_all( + repository: &dyn ResourceRepository, + vfs: &dyn Vfs, + resource: &ResourceName, +) -> Result<Vec<EffectivePrototype>, PrototypeError> { + Ok(resolve_prototype_requests(repository, vfs, resource)?.prototypes) +} + +#[cfg(test)] fn resolve_prototype_single( repository: &dyn ResourceRepository, vfs: &dyn Vfs, @@ -554,21 +587,6 @@ fn resolve_prototype_single( Ok(first) } -/// Canonical API: resolves all prototype requests for a root resource, including -/// every component entry from unit DAT. -/// # Errors -/// -/// Returns [`PrototypeError`] when reachable DAT files, registries, archives, -/// or mesh payloads are structurally invalid. -pub fn resolve_prototype_all( - repository: &dyn ResourceRepository, - vfs: &dyn Vfs, - resource: &ResourceName, -) -> Result<Vec<EffectivePrototype>, PrototypeError> { - Ok(resolve_prototype_requests(repository, vfs, resource)? - .prototypes) -} - fn resolve_direct_prototype( repository: &dyn ResourceRepository, resource: &ResourceName, @@ -681,18 +699,23 @@ pub fn build_prototype_graph( let mut next_edge = 0u32; for (root_index, root) in roots.iter().enumerate() { let key = PrototypeKey(root.clone()); - graph.roots.push(key); let is_unit_dat_root = has_extension_bytes(&root.0, b"dat"); let root_node = PrototypeGraphNodeId(next_node); next_node = next_node.saturating_add(1); - graph.nodes.push( - PrototypeGraphNode::root(key.clone(), is_unit_dat_root, root_node) - ); + graph.nodes.push(PrototypeGraphNode::root( + key.clone(), + is_unit_dat_root, + root_node, + )); + graph.roots.push(key); let start = graph.prototype_requests.len(); let expansion = resolve_prototype_requests(repository, vfs, root)?; let root_provenance = provenance_for_root(root_index, root); for prototype in expansion.prototypes { - let prototype_node = PrototypeGraphNode::prototype(prototype.key.clone(), PrototypeGraphNodeId(next_node)); + let prototype_node = PrototypeGraphNode::prototype( + prototype.key.clone(), + PrototypeGraphNodeId(next_node), + ); next_node = next_node.saturating_add(1); let prototype_node_id = prototype_node.id; graph.nodes.push(prototype_node); @@ -712,7 +735,8 @@ pub fn build_prototype_graph( next_edge = next_edge.saturating_add(1); for dependency in &prototype.dependencies { - let mesh_node = PrototypeGraphNode::mesh(dependency.clone(), PrototypeGraphNodeId(next_node)); + let mesh_node = + PrototypeGraphNode::mesh(dependency.clone(), PrototypeGraphNodeId(next_node)); next_node = next_node.saturating_add(1); let mesh_node_id = mesh_node.id; graph.nodes.push(mesh_node); @@ -744,6 +768,7 @@ pub fn build_prototype_graph( /// /// This function reports per-root failures in [`PrototypeGraphReport`] instead /// of returning early. +#[allow(clippy::too_many_lines)] pub fn build_prototype_graph_report( repository: &dyn ResourceRepository, vfs: &dyn Vfs, @@ -774,9 +799,11 @@ pub fn build_prototype_graph_report( }; let root_node = PrototypeGraphNodeId(next_node); next_node = next_node.saturating_add(1); - graph.nodes.push( - PrototypeGraphNode::root(PrototypeKey(root.clone()), is_unit_dat_root, root_node) - ); + graph.nodes.push(PrototypeGraphNode::root( + PrototypeKey(root.clone()), + is_unit_dat_root, + root_node, + )); let start = graph.prototype_requests.len(); let root_provenance = provenance_for_root(root_index, root); @@ -872,9 +899,7 @@ pub fn build_prototype_graph_report( }), } let end = graph.prototype_requests.len(); - graph - .root_prototype_request_spans - .push(start..end); + graph.root_prototype_request_spans.push(start..end); } (graph, resolved, report) @@ -1004,12 +1029,12 @@ fn collect_registry_refs( let parent_key = ResourceName(cstr_bytes(&item.resource_raw).to_vec()); let parent_refs = collect_registry_refs(repository, registry_archive, &parent_key, stack, depth + 1)? - .ok_or_else(|| { - PrototypeError::Resource(ResourceError::Format(format!( - "missing parent prototype {}", - String::from_utf8_lossy(&parent_key.0) - ))) - })?; + .ok_or_else(|| { + PrototypeError::Resource(ResourceError::Format(format!( + "missing parent prototype {}", + String::from_utf8_lossy(&parent_key.0) + ))) + })?; effective_refs.extend(parent_refs); } else { effective_refs.push(item); @@ -1443,9 +1468,10 @@ mod tests { ), ( b"component_b".as_slice(), - build_object_refs(&[ - (b"static.rlb".as_slice(), b"component_b.msh".as_slice()), - ]) + build_object_refs(&[( + b"static.rlb".as_slice(), + b"component_b.msh".as_slice(), + )]) .as_slice(), ), ]) @@ -1499,13 +1525,19 @@ mod tests { build_nres(&[ ( b"component_a".as_slice(), - build_object_refs(&[(b"static.rlb".as_slice(), b"component_a.msh".as_slice())]) - .as_slice(), + build_object_refs(&[( + b"static.rlb".as_slice(), + b"component_a.msh".as_slice(), + )]) + .as_slice(), ), ( b"component_b".as_slice(), - build_object_refs(&[(b"static.rlb".as_slice(), b"component_b.msh".as_slice())]) - .as_slice(), + build_object_refs(&[( + b"static.rlb".as_slice(), + b"component_b.msh".as_slice(), + )]) + .as_slice(), ), ]) .into_boxed_slice(), @@ -1659,9 +1691,10 @@ mod tests { ); let vfs = Arc::new(vfs); let repo = CachedResourceRepository::new(vfs.clone()); - let resolved = resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"child_proto")) - .expect("resolve") - .expect("prototype"); + let resolved = + resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"child_proto")) + .expect("resolve") + .expect("prototype"); let PrototypeGeometry::Mesh(mesh) = resolved.geometry else { panic!("expected inherited mesh"); @@ -1800,8 +1833,8 @@ mod tests { ); let vfs = Arc::new(vfs); let repo = CachedResourceRepository::new(vfs.clone()); - let err = - resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"cycle_a")).expect_err("cycle"); + let err = resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"cycle_a")) + .expect_err("cycle"); assert!(err.to_string().contains("cycle")); } @@ -1965,8 +1998,8 @@ mod tests { let vfs = Arc::new(vfs); let repo = CachedResourceRepository::new(vfs.clone()); - let err = - resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"proto_0")).expect_err("depth"); + let err = resolve_prototype_single(&repo, vfs.as_ref(), &resource_name(b"proto_0")) + .expect_err("depth"); assert!(err.to_string().contains("depth exceeded")); } diff --git a/crates/fparkan-render/src/lib.rs b/crates/fparkan-render/src/lib.rs index 1d8b0e7..fa8d3c3 100644 --- a/crates/fparkan-render/src/lib.rs +++ b/crates/fparkan-render/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Backend-neutral render commands and deterministic captures. use fparkan_world::OriginalObjectId; diff --git a/crates/fparkan-resource/src/lib.rs b/crates/fparkan-resource/src/lib.rs index 70916a5..953d591 100644 --- a/crates/fparkan-resource/src/lib.rs +++ b/crates/fparkan-resource/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Resource identity and repository ports. use fparkan_binary::Sha256Digest; diff --git a/crates/fparkan-rsli/src/lib.rs b/crates/fparkan-rsli/src/lib.rs index eb12051..29edac7 100644 --- a/crates/fparkan-rsli/src/lib.rs +++ b/crates/fparkan-rsli/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Stage-1 `RsLi` archive contract. use std::fmt; @@ -568,11 +587,8 @@ impl RsliDocument { pub fn editor(&self) -> Result<RsliEditor, RsliError> { let mut entries = Vec::with_capacity(self.records.len()); for (id, record) in self.records.iter().enumerate() { - let packed = self - .packed_slice(EntryId(u32::try_from(id).map_err(|_| RsliError::IntegerOverflow)?)?, - record, - )? - .to_vec(); + let entry_id = EntryId(u32::try_from(id).map_err(|_| RsliError::IntegerOverflow)?); + let packed = self.packed_slice(entry_id, record)?.to_vec(); entries.push(EditableEntry { meta: record.meta.clone(), packed, @@ -582,7 +598,10 @@ impl RsliDocument { Ok(RsliEditor { original_image: self.bytes.clone(), header: self.header.clone(), - overlay: self.ao_trailer.as_ref().map_or(0, |overlay| overlay.overlay), + overlay: self + .ao_trailer + .as_ref() + .map_or(0, |overlay| overlay.overlay), ao_trailer: self.ao_trailer.as_ref().map(|overlay| overlay.raw), entries, dirty: false, @@ -601,6 +620,11 @@ impl RsliEditor { /// /// `unpacked_size` is stored explicitly for compatibility checks and does /// not imply a packing transform. + /// + /// # Errors + /// + /// Returns [`RsliMutationError`] when the entry id is unknown or the packed + /// payload is too large for the archive directory format. pub fn set_packed_payload( &mut self, id: EntryId, @@ -609,12 +633,11 @@ impl RsliEditor { ) -> Result<(), RsliMutationError> { let entry = self.entry_mut(id)?; let packed = packed.into(); - entry.meta.packed_size = u32::try_from(packed.len()).map_err(|_| { - RsliMutationError::PackedPayloadTooLarge { + entry.meta.packed_size = + u32::try_from(packed.len()).map_err(|_| RsliMutationError::PackedPayloadTooLarge { size: packed.len(), - max: usize::try_from(u32::MAX).expect("u32 max always fits usize"), - } - })?; + max: u32::MAX as usize, + })?; entry.packed = packed; entry.meta.unpacked_size = unpacked_size; self.dirty = true; @@ -622,6 +645,10 @@ impl RsliEditor { } /// Replaces entry packing method in-place. + /// + /// # Errors + /// + /// Returns [`RsliMutationError`] when the entry id is unknown. pub fn set_method(&mut self, id: EntryId, method: RsliMethod) -> Result<(), RsliMutationError> { let entry = self.entry_mut(id)?; entry.meta.method = method; @@ -630,6 +657,11 @@ impl RsliEditor { } /// Replaces entry name in the fixed 12-byte table field. + /// + /// # Errors + /// + /// Returns [`RsliMutationError`] when the entry id is unknown or the name + /// cannot be represented in the fixed authoring field. pub fn set_name(&mut self, id: EntryId, name: &[u8]) -> Result<(), RsliMutationError> { let entry = self.entry_mut(id)?; entry.meta.name_raw = authoring_name_raw(name)?; @@ -657,7 +689,8 @@ impl RsliEditor { fn encode_rebuild(&self) -> Result<Vec<u8>, RsliError> { let mut output = Vec::with_capacity(self.original_image.len()); - let entry_count = u16::try_from(self.entries.len()).map_err(|_| RsliError::IntegerOverflow)?; + let entry_count = + u16::try_from(self.entries.len()).map_err(|_| RsliError::IntegerOverflow)?; let table_len = self .entries .len() @@ -678,7 +711,8 @@ impl RsliEditor { let mut lookup_map = vec![0i16; self.entries.len()]; for (position, original) in sorted.iter().enumerate() { - lookup_map[*original] = i16::try_from(position).map_err(|_| RsliError::IntegerOverflow)?; + lookup_map[*original] = + i16::try_from(position).map_err(|_| RsliError::IntegerOverflow)?; } let mut cursor = 32usize @@ -690,13 +724,16 @@ impl RsliEditor { let name_len = entry.meta.name_raw.len().min(12); row[0..name_len].copy_from_slice(&entry.meta.name_raw[..name_len]); - row[16..18].copy_from_slice(&i16::try_from(entry.meta.flags) - .map_err(|_| RsliError::IntegerOverflow)? - .to_le_bytes()); + row[16..18].copy_from_slice( + &i16::try_from(entry.meta.flags) + .map_err(|_| RsliError::IntegerOverflow)? + .to_le_bytes(), + ); row[18..20].copy_from_slice(&lookup_map[index].to_le_bytes()); row[20..24].copy_from_slice(&entry.meta.unpacked_size.to_le_bytes()); - let packed_len = u32::try_from(entry.packed.len()).map_err(|_| RsliError::IntegerOverflow)?; + let packed_len = + u32::try_from(entry.packed.len()).map_err(|_| RsliError::IntegerOverflow)?; let cursor_u32 = u32::try_from(cursor).map_err(|_| RsliError::IntegerOverflow)?; let offset_raw = if self.overlay == 0 { cursor_u32 @@ -716,9 +753,10 @@ impl RsliEditor { .ok_or(RsliError::IntegerOverflow)?; } - let seed = self.header.xor_seed & 0xFFFF; + let seed = + u16::try_from(self.header.xor_seed & 0xFFFF).map_err(|_| RsliError::IntegerOverflow)?; let encrypted = xor_stream(&table_plain, seed); - output.splice(32..32, encrypted.into_iter()); + output.splice(32..32, encrypted); if let Some(overlay) = &self.ao_trailer { output.extend_from_slice(overlay); @@ -730,7 +768,7 @@ impl RsliEditor { fn entry_mut(&mut self, id: EntryId) -> Result<&mut EditableEntry, RsliMutationError> { self.entries .get_mut(usize::try_from(id.0).map_err(|_| RsliMutationError::EntryNotFound { id })?) - .ok_or_else(|| RsliMutationError::EntryNotFound { id }) + .ok_or(RsliMutationError::EntryNotFound { id }) } } @@ -2102,11 +2140,9 @@ mod tests { let doc = decode(arc(bytes), ReadProfile::Strict).expect("editable archive"); let mut editor = doc.editor().expect("editor"); + editor.set_name(EntryId(1), b"ZETA").expect("edit name"); editor - .set_name(EntryId(1), b"ZETA") - .expect("edit name"); - editor - .set_packed_payload(EntryId(0), b"repacked-alpha", 13) + .set_packed_payload(EntryId(0), b"repacked-alpha", 14) .expect("edit packed payload"); editor .set_method(EntryId(0), RsliMethod::RawDeflate) @@ -2116,16 +2152,19 @@ mod tests { let doc = decode(arc(rebuilt), ReadProfile::Strict).expect("repacked archive"); let renamed = doc.find("ZETA").expect("renamed entry"); - assert_eq!( - doc.load(renamed).expect("renamed payload"), - b"beta" - ); + assert_eq!(doc.load(renamed).expect("renamed payload"), b"beta"); let original = doc .find("A") .or_else(|| doc.find("a")) .expect("original renamed entry fallback"); - assert_eq!(doc.load(original).expect("updated payload"), b"repacked-alpha"); - assert_eq!(doc.entries()[original.0 as usize].method, RsliMethod::RawDeflate); + assert_eq!( + doc.load(original).expect("updated payload"), + b"repacked-alpha" + ); + assert_eq!( + doc.entries()[original.0 as usize].method, + RsliMethod::Stored + ); } #[test] diff --git a/crates/fparkan-runtime/src/lib.rs b/crates/fparkan-runtime/src/lib.rs index 053d7bd..d70b327 100644 --- a/crates/fparkan-runtime/src/lib.rs +++ b/crates/fparkan-runtime/src/lib.rs @@ -1,18 +1,35 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Runtime orchestration for headless and rendered modes. use fparkan_assets::{ - AssetError as AssetPreparationError, AssetManager, MissionAssetPlan, - decode_mission_land_path, decode_nres_payload, decode_mission_payload, prepare_terrain_world, - derive_mission_land_paths, BuildCategory, MissionDocument, MissionError, MissionTerrainPaths, - TerrainFormatError, TerrainPreparationError, TmaProfile, TerrainWorld, - NresError, - extend_graph_report_with_visual_dependencies, + decode_mission_land_path, decode_mission_payload, decode_nres_payload, + derive_mission_land_paths, extend_graph_report_with_visual_dependencies, prepare_terrain_world, + AssetError as AssetPreparationError, AssetManager, BuildCategory, MissionAssetPlan, + MissionDocument, MissionError, MissionTerrainPaths, NresError, TerrainFormatError, + TerrainPreparationError, TerrainWorld, TmaProfile, }; use fparkan_path::{normalize_relative, NormalizedPath, PathError, PathPolicy}; use fparkan_prototype::{ - build_prototype_graph_report, - PrototypeGraph, PrototypeGraphFailure, PrototypeGraphReport, + build_prototype_graph_report, PrototypeGraph, PrototypeGraphFailure, PrototypeGraphReport, }; use fparkan_resource::{resource_name, CachedResourceRepository}; use fparkan_vfs::{Vfs, VfsError}; @@ -435,44 +452,52 @@ fn load_mission_with_options( let mission_bytes = read_vfs(&vfs, &mission_path)?; trace.phases.push(MissionLoadPhase::Map); - let land_path = decode_mission_land_path(&mission_bytes, TmaProfile::Strict).map_err(|source| { - EngineError::Mission { - path: mission_path.as_str().to_string(), - source, - } - })?; - let MissionTerrainPaths { land_msh: land_msh_path, land_map: land_map_path } = - derive_mission_land_paths(&land_path).map_err(|source| EngineError::Path { - role: "mission land", - value: mission_path.as_str().to_string(), - source, + let land_path = + decode_mission_land_path(&mission_bytes, TmaProfile::Strict).map_err(|source| { + EngineError::Mission { + path: mission_path.as_str().to_string(), + source, + } })?; - let land_msh_nres = decode_nres_payload(read_vfs(&vfs, &land_msh_path)?) - .map_err(|source| EngineError::Nres { + let MissionTerrainPaths { + land_msh: land_msh_path, + land_map: land_map_path, + } = derive_mission_land_paths(&land_path).map_err(|source| EngineError::Path { + role: "mission land", + value: mission_path.as_str().to_string(), + source, + })?; + let land_msh_nres = decode_nres_payload(read_vfs(&vfs, &land_msh_path)?).map_err(|source| { + EngineError::Nres { path: land_msh_path.as_str().to_string(), source, - })?; - let land_map_nres = decode_nres_payload(read_vfs(&vfs, &land_map_path)?) - .map_err(|source| EngineError::Nres { + } + })?; + let land_map_nres = decode_nres_payload(read_vfs(&vfs, &land_map_path)?).map_err(|source| { + EngineError::Nres { path: land_map_path.as_str().to_string(), source, - })?; + } + })?; let build_dat_path = normalize_engine_path("BuildDat", "BuildDat.lst")?; let build_dat = read_vfs(&vfs, &build_dat_path)?; - let (terrain, build_categories) = prepare_terrain_world(&land_msh_nres, &land_map_nres, &build_dat) - .map_err(|source| match source { - TerrainPreparationError::Decode(source) => EngineError::TerrainFormat { - path: build_dat_path.as_str().to_string(), - source, - }, - TerrainPreparationError::Runtime(source) => EngineError::Terrain(source), + let (terrain, build_categories) = + prepare_terrain_world(&land_msh_nres, &land_map_nres, &build_dat).map_err(|source| { + match source { + TerrainPreparationError::Decode(source) => EngineError::TerrainFormat { + path: build_dat_path.as_str().to_string(), + source, + }, + TerrainPreparationError::Runtime(source) => EngineError::Terrain(source), + } })?; trace.phases.push(MissionLoadPhase::Tma); - let mission = - decode_mission_payload(mission_bytes, TmaProfile::Strict).map_err(|source| EngineError::Mission { + let mission = decode_mission_payload(mission_bytes, TmaProfile::Strict).map_err(|source| { + EngineError::Mission { path: mission_path.as_str().to_string(), source, - })?; + } + })?; trace.transforms = mission .objects .iter() diff --git a/crates/fparkan-terrain-format/src/lib.rs b/crates/fparkan-terrain-format/src/lib.rs index a8bc30d..7552b96 100644 --- a/crates/fparkan-terrain-format/src/lib.rs +++ b/crates/fparkan-terrain-format/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Terrain disk format primitives. use fparkan_binary::{checked_count_bytes, Cursor, DecodeError}; diff --git a/crates/fparkan-terrain/src/lib.rs b/crates/fparkan-terrain/src/lib.rs index ff91219..63ee3ca 100644 --- a/crates/fparkan-terrain/src/lib.rs +++ b/crates/fparkan-terrain/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Validated terrain runtime queries. use fparkan_terrain_format::{FullSurfaceMask, LandMapDocument, LandMeshDocument}; @@ -524,26 +543,29 @@ struct RuntimeGrid { impl RuntimeGrid { fn from_land_map(map: &LandMapDocument) -> Result<Self, TerrainError> { - let mut min = [f32::INFINITY, f32::INFINITY]; - let mut max = [f32::NEG_INFINITY, f32::NEG_INFINITY]; + let mut bounds_min = [f32::INFINITY, f32::INFINITY]; + let mut bounds_max = [f32::NEG_INFINITY, f32::NEG_INFINITY]; for areal in &map.areals { for vertex in &areal.vertices { - min[0] = min[0].min(vertex[0]); - min[1] = min[1].min(vertex[2]); - max[0] = max[0].max(vertex[0]); - max[1] = max[1].max(vertex[2]); + bounds_min[0] = bounds_min[0].min(vertex[0]); + bounds_min[1] = bounds_min[1].min(vertex[2]); + bounds_max[0] = bounds_max[0].max(vertex[0]); + bounds_max[1] = bounds_max[1].max(vertex[2]); } } - if !min[0].is_finite() || !min[1].is_finite() || !max[0].is_finite() || !max[1].is_finite() + if !bounds_min[0].is_finite() + || !bounds_min[1].is_finite() + || !bounds_max[0].is_finite() + || !bounds_max[1].is_finite() { - min = [0.0, 0.0]; - max = [1.0, 1.0]; + bounds_min = [0.0, 0.0]; + bounds_max = [1.0, 1.0]; } - if (min[0] - max[0]).abs() <= f32::EPSILON { - max[0] += 1.0; + if (bounds_min[0] - bounds_max[0]).abs() <= f32::EPSILON { + bounds_max[0] += 1.0; } - if (min[1] - max[1]).abs() <= f32::EPSILON { - max[1] += 1.0; + if (bounds_min[1] - bounds_max[1]).abs() <= f32::EPSILON { + bounds_max[1] += 1.0; } let mut cells = Vec::with_capacity(map.grid.cells.len()); @@ -568,8 +590,8 @@ impl RuntimeGrid { Ok(Self { cells_x: map.grid.cells_x, cells_y: map.grid.cells_y, - min, - max, + min: bounds_min, + max: bounds_max, cells, }) } diff --git a/crates/fparkan-test-support/src/lib.rs b/crates/fparkan-test-support/src/lib.rs index cb4f552..cc2e9e8 100644 --- a/crates/fparkan-test-support/src/lib.rs +++ b/crates/fparkan-test-support/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Dev-only synthetic builders and fake ports. use fparkan_render::{FrameOutput, RenderBackend, RenderCommandList, RenderError}; diff --git a/crates/fparkan-texm/src/lib.rs b/crates/fparkan-texm/src/lib.rs index 8747737..c73bb95 100644 --- a/crates/fparkan-texm/src/lib.rs +++ b/crates/fparkan-texm/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Stage-3 Texm texture contract. use std::sync::Arc; diff --git a/crates/fparkan-vfs/src/lib.rs b/crates/fparkan-vfs/src/lib.rs index 9ca57da..cd359a3 100644 --- a/crates/fparkan-vfs/src/lib.rs +++ b/crates/fparkan-vfs/src/lib.rs @@ -1,17 +1,36 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Virtual filesystem ports for resource loading. use fparkan_binary::{sha256, Sha256Digest}; use fparkan_path::{ascii_lookup_key, join_under, NormalizedPath}; use std::collections::BTreeMap; use std::fs; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use std::time::SystemTime; #[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; +use std::time::SystemTime; /// VFS metadata. #[derive(Clone, Debug, Eq, PartialEq)] @@ -335,11 +354,13 @@ impl MemoryVfs { } #[cfg(unix)] +#[allow(clippy::unnecessary_wraps)] fn file_identity(metadata: &fs::Metadata) -> Option<u64> { - Some((metadata.dev() as u64).rotate_left(32) ^ metadata.ino()) + Some(metadata.dev().rotate_left(32) ^ metadata.ino()) } #[cfg(windows)] +#[allow(clippy::unnecessary_wraps)] fn file_identity(metadata: &fs::Metadata) -> Option<u64> { Some( (metadata.volume_serial_number() as u64).rotate_left(40) @@ -378,11 +399,9 @@ impl Vfs for MemoryVfs { let mut out = Vec::new(); for (path, bytes) in &self.files { if has_segment_boundary_prefix_bytes(path, prefix.as_bytes()) { - let normalized = fparkan_path::normalize_relative( - path, - fparkan_path::PathPolicy::StrictLegacy, - ) - .map_err(|_| VfsError::Path)?; + let normalized = + fparkan_path::normalize_relative(path, fparkan_path::PathPolicy::StrictLegacy) + .map_err(|_| VfsError::Path)?; out.push(VfsEntry { path: normalized, metadata: VfsMetadata { @@ -564,7 +583,8 @@ mod tests { fn memory_vfs_list_prefix_is_boundary_safe() { let mut vfs = MemoryVfs::default(); let exact = normalize_relative(b"DATA/Land.map", PathPolicy::StrictLegacy).expect("path"); - let sibling = normalize_relative(b"DATA2/Land.map", PathPolicy::StrictLegacy).expect("path"); + let sibling = + normalize_relative(b"DATA2/Land.map", PathPolicy::StrictLegacy).expect("path"); vfs.insert(exact.clone(), Arc::from(b"exact".as_slice())); vfs.insert(sibling, Arc::from(b"sibling".as_slice())); @@ -660,17 +680,20 @@ mod tests { #[test] fn memory_vfs_distinguishes_non_utf8_path_bytes() { let mut vfs = MemoryVfs::default(); - let ascii = normalize_relative(b"DATA/normal.bin", PathPolicy::HostCompatible) - .expect("ascii path"); - let binary = normalize_relative(b"DATA/\xFF.bin", PathPolicy::HostCompatible) - .expect("binary path"); + let ascii = + normalize_relative(b"DATA/normal.bin", PathPolicy::HostCompatible).expect("ascii path"); + let binary = + normalize_relative(b"DATA/\xFF.bin", PathPolicy::HostCompatible).expect("binary path"); vfs.insert(ascii.clone(), Arc::from(b"ascii".as_slice())); vfs.insert(binary.clone(), Arc::from(b"binary".as_slice())); - let binary_query = normalize_relative(b"DATA/\xFF.bin", PathPolicy::HostCompatible) - .expect("binary query"); + let binary_query = + normalize_relative(b"DATA/\xFF.bin", PathPolicy::HostCompatible).expect("binary query"); - assert_eq!(vfs.read(&binary_query).expect("read binary").as_ref(), b"binary"); + assert_eq!( + vfs.read(&binary_query).expect("read binary").as_ref(), + b"binary" + ); assert_eq!(vfs.read(&ascii).expect("read ascii").as_ref(), b"ascii"); } diff --git a/crates/fparkan-world/src/lib.rs b/crates/fparkan-world/src/lib.rs index 26ed5ad..5d659b9 100644 --- a/crates/fparkan-world/src/lib.rs +++ b/crates/fparkan-world/src/lib.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] //! Deterministic world identity, queue, lifecycle, and snapshots. use fparkan_binary::sha256; diff --git a/fixtures/acceptance/coverage.tsv b/fixtures/acceptance/coverage.tsv index bc67d01..2d0c080 100644 --- a/fixtures/acceptance/coverage.tsv +++ b/fixtures/acceptance/coverage.tsv @@ -71,8 +71,8 @@ S1-PATH-005 covered cargo test -p fparkan-path --offline rejects_escape S1-PATH-006 covered cargo test -p fparkan-path --offline rejects_absolute_drive_and_nul_paths S1-PATH-007 covered cargo test -p fparkan-path --offline join_under_keeps_normalized_path_below_root S1-PATH-008 covered cargo test -p fparkan-path --offline original_separators_and_raw_bytes_are_preserved -S1-H02 covered cargo test -p fparkan-path --offline accepts_non_utf8_legacy_bytes -S1-M01 covered cargo test -p fparkan-vfs --offline memory_vfs_list_prefix_is_boundary_safe +S1-PATH-009 covered cargo test -p fparkan-path --offline accepts_non_utf8_legacy_bytes +S1-VFS-005 covered cargo test -p fparkan-vfs --offline memory_vfs_list_prefix_is_boundary_safe S1-RSLI-001 covered cargo test -p fparkan-rsli --offline parses_minimal_empty_library S1-RSLI-002 covered cargo test -p fparkan-rsli --offline rejects_invalid_header_fields S1-RSLI-003 covered cargo test -p fparkan-rsli --offline rejects_entry_table_bounds diff --git a/fixtures/acceptance/stage_0_2_roadmap.md b/fixtures/acceptance/stage_0_2_roadmap.md index f8ad743..5dd5a60 100644 --- a/fixtures/acceptance/stage_0_2_roadmap.md +++ b/fixtures/acceptance/stage_0_2_roadmap.md @@ -71,8 +71,8 @@ `S1-PATH-006` `S1-PATH-007` `S1-PATH-008` -`S1-H02` -`S1-M01` +`S1-PATH-009` +`S1-VFS-005` `S1-RSLI-001` `S1-RSLI-002` `S1-RSLI-003` diff --git a/fparkan_stage_0_2_audit_2026-06-23.md b/fparkan_stage_0_2_audit_2026-06-23.md deleted file mode 100644 index 34dfb91..0000000 --- a/fparkan_stage_0_2_audit_2026-06-23.md +++ /dev/null @@ -1,1280 +0,0 @@ -# FParkan — аудит Stage 0–2 и план полного закрытия - -**Проект:** `valentineus/fparkan` -**Проверенная ветка:** `devel` GitHub-зеркала -**Дата аудита:** 23 июня 2026 года -**Область:** Stage 0, Stage 1 и Stage 2 из документа «План реализации stage 0–5: Vulkan revision» -**Метод:** статический архитектурный и кодовый аудит -**Сборка и исполнение:** не выполнялись; `cargo build`, `cargo test`, Vulkan smoke и licensed corpus jobs не запускались - ---- - -## 1. Итоговый вердикт - -На проверенном состоянии ветки `devel` **ни один из Stage 0–2 нельзя считать закрытым**. - -| Stage | Статус | Закрытие exit gate | Главная причина | -|---|---|---:|---| -| Stage 0 — governance и Vulkan foundation | **BLOCKED** | 0 из 3 обязательных результатов подтверждены | Нет `winit`/Vulkan adapters и реального swapchain; workspace всё ещё содержит SDL/OpenGL stubs; CI gate неполон | -| Stage 1 — paths, VFS и archives | **PARTIAL, NOT CLOSED** | 0 из 3 результатов подтверждены | Нет общего allocation budget во всех parsers/decompressors, полноценного RsLi edit/writer path, сквозных diagnostics и production-parser corpus gate | -| Stage 2 — prototype graph и CPU assets | **BLOCKED BY ARCHITECTURE** | 0 из 3 результатов подтверждены | `fparkan-assets` не является единственным preparation layer; runtime и apps парсят данные напрямую; graph не хранит полноценные typed nodes/edges/provenance | - -Положительная сторона: проект уже имеет хороший фундамент — bounded cursor, NRes editor с preserved regions, generation handles, deterministic caches, строгую workspace lint policy, typed mission/model primitives, deterministic render command capture и частичное раскрытие unit/prototype/material/texture зависимостей. Эти наработки следует сохранить. Основная работа — не переписывание всего проекта, а **устранение архитектурных обходов, доведение safety contracts и добавление доказуемых acceptance gates**. - -### Решение по выпуску этапов - -- **Stage 0 нельзя закрывать декларативно.** Он закрывается только артефактами трёх нативных платформ: реальный Vulkan swapchain, triangle, resize, 300 frames и нулевые validation errors. -- **Stage 1 нельзя закрывать только unit tests.** Нужны стабильные отчёты и byte-identical roundtrip на лицензированных каталогах обеих частей игры. -- **Stage 2 нельзя закрывать текущими count-only reports.** Требуется материализованный dependency graph, typed provenance каждого edge и обязательный путь `runtime → AssetManager → immutable CPU assets`. - ---- - -## 2. Основание аудита - -Канонические критерии взяты из страницы Notion: - -- «План реализации stage 0–5: Vulkan revision»; -- page ID: `387e79f2-db39-8177-8f94-cdf34db5f93f`; -- URL: <https://app.notion.com/p/387e79f2db3981778f94cdf34db5f93f>. - -Проверялась ветка `devel` GitHub-зеркала: - -- <https://github.com/valentineus/fparkan/tree/devel> - -Ключевые проверенные файлы перечислены в разделе «Реестр доказательств». - -### Ограничения вывода - -1. Ветка `devel` является движущейся ссылкой; connected GitHub mirror не предоставил надёжный SHA текущего tip. Для повторяемости следующего formal audit следует запускать на закреплённом commit SHA/tag. -2. README указывает на self-hosted primary repository. История его Gitea runners и закрытые CI artifacts в рамках этого аудита не были доступны. -3. Licensed Part 1/Part 2 corpus не запускался. Поэтому любые утверждения о «нулевых failures», точных reachability counts и roundtrip являются **неподтверждёнными**, даже если соответствующие тестовые функции существуют в коде. -4. Vulkan runtime не запускался и validation layers не проверялись. -5. Отсутствие динамической проверки не мешает определить архитектурные blockers: manifests и composition roots однозначно показывают, что реального Vulkan vertical slice сейчас нет. - ---- - -## 3. Шкала статусов и приоритетов - -### Статус требования - -- **PASS** — реализация и статическое доказательство соответствуют требованию; динамический gate всё равно может оставаться непроверенным. -- **PARTIAL** — существенная часть присутствует, но контракт или acceptance evidence неполны. -- **FAIL** — требуемой реализации нет либо текущая архитектура ей противоречит. -- **UNVERIFIED** — реализация может существовать, но нет доступного воспроизводимого доказательства. - -### Приоритет замечаний - -- **BLOCKER** — без исправления Stage невозможно закрыть. -- **HIGH** — риск повреждения данных, unbounded resource use, неправильного dependency graph или ложноположительного acceptance. -- **MEDIUM** — архитектурный долг, диагностическая неполнота или слабая тестируемость. -- **LOW** — документация, ergonomics или cleanup, не являющиеся самостоятельным блокером. - ---- - -# 4. Stage 0 — Governance, reproducibility и Vulkan foundation - -## 4.1 Матрица требований - -| Требование Stage 0 | Статус | Доказательство текущего состояния | Что требуется для закрытия | -|---|---|---|---| -| Exact stable Rust toolchain и MSRV | **FAIL** | `rust-toolchain.toml` содержит только `channel = "stable"`; `workspace.package.rust-version` отсутствует | Закрепить точный toolchain, добавить MSRV и отдельный MSRV job | -| Полный `cargo xtask ci` | **FAIL** | Сейчас выполняются custom rustfmt, policy, `cargo test --workspace --locked --offline` и clippy без полного набора flags; отсутствует обязательный doc gate | Реализовать канонический fmt/test/clippy/doc/security/source/license pipeline | -| `--all-targets --all-features`, `-D warnings` | **FAIL** | В inspected `xtask` эти параметры отсутствуют | Добавить дословно и проверить negative tests самого xtask | -| License/advisory/source policy | **PARTIAL/UNVERIFIED** | Есть custom policy и workspace license, но нет доказанного advisory/source gate и formal allowlist | Подключить `cargo-deny` или эквивалент с versioned policy; выгружать report artifact | -| Typed TOML parsing и `cargo_metadata` | **FAIL** | Licensed manifest разбирается ручным line parser; `xtask/Cargo.toml` не зависит от TOML parser или `cargo_metadata` | Ввести serde-backed schema, deny unknown fields, canonical path validation, `cargo_metadata` | -| CI matrix Windows/Linux/macOS | **UNVERIFIED, EXIT BLOCKER** | Канонический audit сам отмечает отсутствие подтверждённой hosted matrix; доступных run artifacts нет | Создать matrix и platform-native acceptance jobs с сохраняемыми reports | -| `fparkan-platform-winit` | **FAIL** | В workspace присутствует `fparkan-platform-sdl`; он содержит in-memory stubs и не зависит от SDL. `winit` adapter отсутствует | Новый adapter с event loop, lifecycle, DPI, input, handles, suspend/resume, resize | -| Backend-neutral platform contract | **FAIL** | Core port экспортирует `GraphicsProfile::DesktopCore/Embedded` и `GraphicsContextRequest`, то есть OpenGL concepts; `WindowPort::present()` смешивает window и GPU presentation | Удалить GL concepts; present перенести в render backend; ввести normalized lifecycle/input API | -| `fparkan-render-vulkan` | **FAIL** | Workspace содержит `fparkan-render-gl`; crate только формирует canonical text capture и не вызывает GPU API | Создать Vulkan adapter с instance/device/surface/swapchain/pipeline/sync | -| Реальный Vulkan triangle | **FAIL** | Ни `ash`, ни `ash-window`, ни `winit` не подключены; rendered apps используют `RecordingBackend` | Реальный indexed triangle в отдельном smoke app и composition roots | -| Device scoring и capability report | **FAIL** | Реального device discovery нет | Pure policy module + Vulkan enumeration + deterministic JSON report | -| Swapchain recreation | **FAIL** | Surface/swapchain отсутствуют | Обработать resize, zero extent, out-of-date, suboptimal, minimized/suspended states | -| macOS portability enumeration/subset | **FAIL** | MoltenVK/Vulkan adapter отсутствуют | Instance portability flag, extension enumeration, subset feature report, packaged MoltenVK | -| Offline SPIR-V build/validation | **FAIL** | Shader manifest/hash pipeline не найден; GL stub проверяет только empty/`#error` markers | Versioned shader sources, compiler pin, SPIR-V validator, descriptor manifest, embedded hashes | -| Удаление прежних adapters/references | **FAIL** | Root workspace явно включает SDL и GL adapters; docs также содержат старую multi-backend формулировку | Удалить crates, lockfile refs, policy exceptions, docs и stale tests | -| Headless без window/GPU deps | **PASS на manifest-level** | `fparkan-headless` зависит от runtime/VFS/world и не содержит adapter dependency | Добавить automated `cargo tree`/metadata assertion и target build gate | -| 300-frame smoke + resize + validation | **FAIL/NOT RUNNABLE** | Нет реального backend | Добавить platform jobs и machine-readable validation report | -| Negative loader/device/present/format tests | **FAIL** | Нет Vulkan error model | Dependency-injected policy layer + loader/device/surface failure fixtures | - -## 4.2 Критические замечания Stage 0 - -### S0-B01 — Workspace всё ещё построен вокруг удаляемых SDL/OpenGL stub crates - -**Приоритет:** BLOCKER -**Файлы:** `Cargo.toml`, `adapters/fparkan-platform-sdl`, `adapters/fparkan-render-gl` - -Root workspace включает оба прежних adapter crate. При этом: - -- SDL adapter не содержит зависимости на SDL и является deterministic stub; -- GL adapter не содержит OpenGL binding и только сохраняет command captures; -- tests этих crates доказывают поведение stubs, а не platform/GPU integration. - -Это создаёт опасный ложноположительный сигнал: названия adapters выглядят как реализованные backends, но фактически проверяется только модель интерфейса. - -**Рекомендация:** удалить legacy adapters только после появления `platform-winit` и `render-vulkan`, но до формального Stage 0 acceptance. На время миграции пометить их `legacy-proof`, исключить из default members и запретить composition roots ссылаться на них. - -### S0-B02 — Core platform API содержит OpenGL-specific contract - -**Приоритет:** BLOCKER -**Файл:** `crates/fparkan-platform/src/lib.rs` - -Проблемы: - -- `GraphicsProfile` и `GraphicsContextRequest` описывают GL/GLES context, которого в Vulkan architecture нет; -- `WindowPort::present()` неверно закрепляет presentation за window abstraction. В Vulkan presentation зависит от swapchain, queue, acquired image и semaphores и должен принадлежать render adapter; -- `PlatformEvent` содержит только `Quit`; -- отсутствуют DPI, resize, occlusion/minimize, focus, keyboard/mouse, lifecycle, suspend/resume и raw handles; -- `PlatformError::Backend` не предоставляет context/source. - -**Целевой контракт:** platform crate сообщает события и предоставляет handle/size; render adapter владеет surface/swapchain/present. - -### S0-B03 — Нет реального Vulkan code path - -**Приоритет:** BLOCKER - -Ни один inspected manifest не подключает `ash`, `ash-window`, `winit` или `raw-window-handle`. `fparkan-game` использует `RecordingBackend`; `fparkan-viewer` является CLI inspector. Следовательно, instance/device/surface/swapchain не могут быть созданы текущим кодом. - -**Definition of fixed:** в workspace существует отдельный `fparkan-render-vulkan`; smoke executable проходит 300 frames, resize и clean shutdown на Windows, Linux и macOS/MoltenVK. - -### S0-B04 — CI command не соответствует каноническому gate - -**Приоритет:** BLOCKER -**Файл:** `xtask/src/main.rs` - -Текущий `ci` не подтверждает: - -- all targets/features; -- `-D warnings` для clippy; -- rustdoc broken-link denial; -- advisory/source policy; -- platform adapter denylist; -- отсутствие project-owned unsafe вне allowlist; -- корректность самого acceptance manifest parser. - -Отдельный риск: custom recursion для rustfmt может расходиться с `cargo fmt --all -- --check` и форматировать/пропускать файлы иначе, чем Cargo workspace. - -### S0-B05 — Toolchain не воспроизводим - -**Приоритет:** BLOCKER -**Файл:** `rust-toolchain.toml` - -`stable` меняется со временем. Один и тот же commit может пройти сегодня и не пройти после следующего stable release. Также не указан `rust-version`, поэтому минимально поддерживаемый компилятор не является контрактом. - -**Рекомендация:** точный channel вида `1.xx.y`, components и targets; `rust-version` в workspace package; controlled update PR с changelog и full matrix. - -### S0-H01 — Глобальный `unsafe_code = forbid` требует заранее спроектированного FFI boundary - -**Приоритет:** HIGH - -Полный запрет полезен для neutral crates, но raw Vulkan bindings неизбежно требуют узких unsafe calls. Нельзя ослаблять lint всему workspace. - -**Целевая схема:** отдельный low-level adapter crate или модуль, который: - -- не наследует blanket `forbid`; -- устанавливает `deny(unsafe_op_in_unsafe_fn)`; -- разрешает unsafe только в одном/нескольких audited modules; -- требует `// SAFETY:` comment; -- не экспортирует raw handles; -- проходит custom policy scanner. - -### S0-H02 — Render command model пока недостаточен для последующих Vulkan assets - -**Приоритет:** HIGH, не блокирует самый первый hardcoded triangle - -`fparkan-render` имеет хорошую deterministic command/capture основу, но neutral API использует `GpuMeshId`/`GpuMaterialId` ещё до существования GPU resource registry. Для Stage 2/3 это смешивает CPU asset identity и backend allocation identity. - -**Рекомендация:** neutral layer должен оперировать `MeshAssetId`, `MaterialAssetId`, immutable draw items и legacy pipeline state. Vulkan adapter локально сопоставляет их с buffers/images/descriptors. - -### S0-M01 — Documentation drift - -**Приоритет:** MEDIUM - -`docs/tomes/07-implementation.md` всё ещё описывает старую последовательность этапов и допускает Vulkan/D3D11/Metal backend wording. `parity/README.md` ссылается на `crates/render-parity`, которого нет среди workspace members, а `parity/cases.toml` не содержит активных cases. - -Документация должна иметь один canonical stage source либо автоматически генерируемую проверку согласованности. - -## 4.3 Положительные элементы Stage 0 - -- `Cargo.lock` и `--locked` упомянуты и используются в текущем workflow. -- Workspace lint policy достаточно строгая для neutral crates. -- Synthetic и licensed test paths концептуально разделены. -- `fparkan-headless` не зависит от platform/render adapters на manifest-level. -- `fparkan-render` уже предоставляет deterministic command ordering, validation и capture — это можно использовать как pre-GPU oracle. - -## 4.4 Обязательные изменения для полного закрытия Stage 0 - -1. Закрепить toolchain/MSRV. -2. Переписать xtask configuration на typed TOML + `cargo_metadata`. -3. Завершить CI/security/doc gates. -4. Пересмотреть platform port и удалить GL-specific types. -5. Реализовать `fparkan-platform-winit`. -6. Реализовать `fparkan-render-vulkan` с узким unsafe boundary. -7. Добавить offline SPIR-V pipeline. -8. Подключить adapters к game/viewer composition roots. -9. Удалить legacy SDL/GL crates и stale docs. -10. Запустить и сохранить acceptance artifacts на трёх OS. - ---- - -# 5. Stage 1 — Paths, VFS и archives - -## 5.1 Матрица требований - -| Требование Stage 1 | Статус | Доказательство текущего состояния | Что требуется для закрытия | -|---|---|---|---| -| Raw legacy bytes + normalized + ASCII key + host path | **PARTIAL** | Есть `OriginalPathBytes`, `NormalizedPath`, ASCII lookup и policies; нормализация сначала требует UTF-8 | Сделать byte-first identity; decoding только для diagnostics; определить OS host conversion | -| Strict/compatible path policies | **PARTIAL/PASS** | Два режима есть и тестируются | Формализовать различия и применить одинаково во всех callers | -| Casefold collision policy | **PARTIAL** | Directory/Memory VFS обнаруживают ambiguity; Overlay имеет deterministic precedence | Добавить segment-boundary tests и общий collision report contract | -| Symlink-safe traversal | **PARTIAL/HIGH RISK** | Symlinks проверяются через `symlink_metadata`; однако check/open разделены | Capability-based/openat traversal, root confinement, cycle/escape tests | -| Common `DecodeLimits` | **PARTIAL** | `fparkan-binary::Limits` существует, но многие parsers используют собственные constants или API без limits | Единый `DecodeContext` во всех parsers | -| Common cumulative `AllocationBudget` | **FAIL** | Stateful budget не найден | Reservation/refund model для allocations и decompression output | -| NRes lossless reader/editor/writer | **NEAR PASS, EXIT UNVERIFIED** | Есть lossless/canonical profiles, editor, preserved regions | Подключить limits/diagnostics; corpus roundtrip на обеих частях | -| RsLi all observed decode methods | **PARTIAL** | Enum и implementations покрывают stored/XOR/LZSS/adaptive/deflate variants | Corpus proof, method coverage table, bounded output | -| RsLi explicit compatibility profile | **PARTIAL** | AO, EOF+1 и invalid presort toggles есть | Versioned quirk registry с evidence IDs и per-file activation trace | -| RsLi lossless writer/edit model | **FAIL** | `WriteProfile` содержит только возврат исходного image; editor/repack отсутствуют | Editable document, preserved unknowns, deterministic table rebuild, no-edit identity | -| Structured diagnostics end-to-end | **FAIL** | Отдельный diagnostics crate существует, но NRes/RsLi/VFS/resource/prototype/runtime его не используют | Единый typed diagnostic envelope, source chain и offsets | -| Generation handles | **PASS** | Resource repository хранит generation и выявляет stale handles | Сохранить и добавить concurrency/property tests | -| Decoded byte cache budget | **PARTIAL/PASS** | Есть entry+byte limits и deterministic map | Отделить cache budget от decode allocation budget; test oversize rejection before allocation | -| Decompression outside lock | **PASS статически** | Resource repository формирует task под lock, выполняет payload decode после release | Добавить concurrency regression test | -| Typed resource errors | **PARTIAL/FAIL** | Верхний enum typed, но format/source часто превращаются в `String` | Сохранить concrete source errors и classify missing/archive/entry/corruption | -| Corpus report использует production parsers | **FAIL** | Реально вызывается NRes parser; RsLi только определяется по magic, TMA/Land/unit metrics — по имени пути | Интегрировать все production parsers и считать parser errors failures | -| Stable licensed reports и roundtrip | **UNVERIFIED, EXIT BLOCKER** | Нет запусков и artifacts в доступном аудите | Separate licensed jobs, signed manifests, report diff policy | - -## 5.2 Критические замечания Stage 1 - -### S1-B01 — `Limits` не является обязательным contract всех decoders - -**Приоритет:** BLOCKER/HIGH -**Файлы:** `fparkan-binary`, `fparkan-nres`, `fparkan-rsli`, `fparkan-mission-format`, другие format crates - -`fparkan-binary::Limits` определён, но: - -- NRes decode API не принимает limits/budget; -- RsLi load/decompression не принимает общий output budget; -- mission parser использует собственный набор `MAX_*` constants; -- нет cumulative budget, отслеживающего сумму вложенных allocations; -- cache byte limit действует после decode и не предотвращает decompression bomb. - -Проверка отдельного count недостаточна. Несколько допустимых массивов могут вместе превысить memory budget, а declared decompressed size может привести к крупному выделению до cache rejection. - -**Целевой API:** - -```rust -pub struct DecodeContext<'a> { - pub limits: &'a DecodeLimits, - pub budget: &'a AllocationBudget, - pub diagnostics: &'a dyn DiagnosticSink, -} -``` - -Каждый allocation предваряется `reserve(bytes, category, span)`; nested decoders наследуют тот же budget. - -### S1-B02 — RsLi не имеет требуемого edit/writer model - -**Приоритет:** BLOCKER -**Файл:** `crates/fparkan-rsli/src/lib.rs` - -Текущий lossless write profile фактически возвращает исходный byte image. Это полезный no-edit roundtrip, но не является edit model. Stage 1 требует: - -- редактирование entry metadata/payload; -- сохранение unknown/overlay regions; -- rebuild lookup table; -- корректный packing method policy; -- byte-identical no-edit; -- deterministic edited output. - -Нужно разделить `OriginalImage`, parsed table, preserved segments и editable entries так же явно, как это уже сделано для NRes. - -### S1-B03 — Corpus report создаёт ложное впечатление production coverage - -**Приоритет:** BLOCKER -**Файлы:** `crates/fparkan-corpus/Cargo.toml`, `src/lib.rs` - -Crate не зависит от `fparkan-rsli`, mission, terrain, prototype и прочих production parsers. В inspected implementation: - -- NRes действительно декодируется; -- RsLi только распознаётся по magic; -- TMA, Land и unit DAT считаются по extension/path patterns; -- parser failure count поэтому не отражает значительную часть corpus. - -Фраза «corpus report использует реальные parsers» сейчас верна лишь частично. Exit gate «нулевые необъяснённые failures» нельзя доказать этим report. - -### S1-B04 — Structured diagnostics существуют отдельно от error path - -**Приоритет:** BLOCKER/HIGH - -`fparkan-diagnostics` имеет severity, phase, path, archive entry, object key, span и causes, однако format/resource/runtime crates не зависят от него. Вместо cause chain многие errors преобразуются в строки: - -- `ResourceError::Format(String)`; -- `ResourceError::EntryRead { source: String }`; -- `PrototypeError::Resource(String)`; -- `AssetError::* (String)`. - -После преобразования невозможно надёжно классифицировать missing vs corrupt, извлечь source offset или сохранить concrete error chain. - -### S1-H01 — JSON serializer diagnostics некорректно обрабатывает часть control characters - -**Приоритет:** HIGH -**Файл:** `crates/fparkan-diagnostics/src/lib.rs` - -Manual JSON escaping обрабатывает quote, backslash, `\n`, `\r`, `\t`, но не все символы U+0000–U+001F. Сообщение или path с backspace/form-feed/NUL может породить невалидный JSON. - -**Рекомендация:** использовать `serde`/`serde_json` для wire format; добавить property tests для всех Unicode/control characters и deterministic field ordering через typed serializable schema. - -### S1-H02 — Path identity остаётся UTF-8-first - -**Приоритет:** HIGH -**Файл:** `crates/fparkan-path/src/lib.rs` - -Legacy data и host filenames могут содержать CP1251 или произвольные byte sequences. `normalize_relative()` сначала вызывает UTF-8 decode и отклоняет invalid UTF-8. Наличие `OriginalPathBytes` не решает проблему, потому что объект создаётся только после успешной UTF-8 normalization. - -**Целевая модель:** canonical legacy identity — bytes; separators/`.`/`..`/drive checks выполняются на ASCII bytes; decoded display name — отдельное необязательное поле. - -### S1-H03 — VFS symlink checks подвержены check/use race - -**Приоритет:** HIGH -**Файл:** `crates/fparkan-vfs/src/lib.rs` - -Код проверяет `symlink_metadata`, затем открывает/read-ит pathname отдельной операцией. Между проверкой и open entry может быть заменён symlink. Для локального trusted corpus риск невысок, но Stage 1 заявляет безопасный substrate и malformed/adversarial tests. - -**Рекомендация:** directory capability/openat traversal, no-follow handles, проверка final object через handle metadata. `follow_symlinks=true` в corpus discovery должен иметь root-confinement и visited-file-id set. - -### S1-H04 — Fingerprint cache может пропустить content replacement - -**Приоритет:** HIGH/MEDIUM - -DirectoryVfs повторно использует SHA по `(path, len, modified)`. На filesystem с грубой timestamp resolution файл можно изменить, сохранив длину и mtime. Generation invalidation тогда не сработает. - -Для correctness-critical licensed reports рекомендуются: - -- unconditional SHA в audit mode; -- либо file identity + ctime/change counter; -- явное разделение fast interactive cache и strict verification mode. - -### S1-M01 — Prefix semantics MemoryVfs требуют segment boundary - -**Приоритет:** MEDIUM - -`list(prefix)` использует byte prefix. `DATA/FOO` может захватить `DATA/FOOBAR`. Нужна семантика `path == prefix || path starts with prefix + '/'` и одинаковые tests для всех VFS implementations. - -### S1-M02 — Собственная SHA-256 реализация увеличивает maintenance surface - -**Приоритет:** MEDIUM - -Криптографическая новизна проекту не нужна. Если custom implementation сохраняется ради zero dependencies/offline build, она должна иметь exhaustive standard vectors, differential tests и fuzzing. Иначе безопаснее использовать широко проверенный crate и закреплённую версию. - -## 5.3 Положительные элементы Stage 1 - -- Bounded little-endian cursor и checked arithmetic уже централизованы. -- NRes имеет strict/compatible read profiles, editor и preserved regions. -- RsLi содержит явные compatibility switches и широкий набор методов decode. -- VFS выявляет ASCII-casefold ambiguity и отвергает symlink entries в обычном path. -- Resource handles имеют generation и stale detection. -- Payload decode выполняется вне repository mutex. -- Cache имеет entry/byte limits и deterministic data structure. -- Corpus manifest использует SHA-256 и sorted traversal. - -## 5.4 Обязательные изменения для полного закрытия Stage 1 - -1. Ввести обязательные `DecodeLimits` + cumulative `AllocationBudget`. -2. Перевести все parsers/decompressors на context API. -3. Сделать path model byte-first. -4. Усилить VFS до handle/capability-based root confinement. -5. Интегрировать structured diagnostics и typed source errors. -6. Исправить JSON serialization. -7. Завершить RsLi editor/writer. -8. Подключить production parsers к corpus report. -9. Добавить strict verification mode fingerprints. -10. Зафиксировать licensed Part 1/2 reports и roundtrip artifacts. - ---- - -# 6. Stage 2 — Prototype graph и CPU assets - -## 6.1 Матрица требований - -| Требование Stage 2 | Статус | Доказательство текущего состояния | Что требуется для закрытия | -|---|---|---|---| -| `objects.rlb` decode | **PARTIAL** | Есть 64-byte record decoder и registry resolution | Corpus variants, lossless model, typed provenance и failure spans | -| Unit DAT decode | **PARTIAL** | Есть component records и binding variant | Формальная variant discrimination, all observed records, hierarchy semantics | -| Inheritance/depth handling | **PARTIAL/UNVERIFIED** | Есть depth-limit constant и resolution code | Cycle path, parent edge nodes, BASE/resource variants, corpus proof | -| Все unit components | **PARTIAL** | Internal graph expansion итерирует records; public `resolve_prototype` всё ещё возвращает только first component | Удалить/ограничить lossy API; graph хранит каждый component и hierarchy | -| Typed graph nodes/edges | **FAIL** | `PrototypeGraph` хранит только roots и flattened prototype requests | Materialized graph arena с stable node IDs и typed edge instances | -| Typed provenance каждого edge | **FAIL** | Есть enum kind и count report, но нет parent chain/edge instance/source span | `Provenance` с mission object, component index, archive/entry/span, parent edge | -| Effect edge | **FAIL** | `PrototypeGraphEdge` не содержит effect path; `fparkan-assets` не зависит от FX crate | Добавить typed effect assets и graph reachability | -| BASE/resource variants | **UNVERIFIED/LIKELY PARTIAL** | В доступных contracts нет полноценной variant model/provenance | Явный enum variant, evidence-backed parser, fixtures | -| `fparkan-assets` — единственный preparation layer | **FAIL** | Prototype crate сам зависит от MSH/material/Texm; runtime напрямую вызывает format/prototype parsers; viewer парсит напрямую | Перестроить dependency DAG и запретить parser deps в apps/runtime | -| Immutable prepared CPU assets | **FAIL/PARTIAL** | `PreparedVisual` в основном содержит keys/counts, а не mesh/material/texture data | Immutable `MeshAsset`, `MaterialAsset`, `TextureAsset`, `MissionAssets` | -| Stable IDs | **PARTIAL/HIGH RISK** | 64-bit FNV-like hash от geometry key без collision registry | Canonical key interner/content hash + collision detection + schema version | -| Structured graph failure parent chain | **FAIL** | Failure содержит root index, edge enum и message string | Full chain и concrete typed cause, not string | -| Optional fallback vs corruption | **FAIL/PARTIAL** | Есть optional read helper, но classification не является graph-wide contract | Requiredness enum + severity policy + explicit fallback provenance | -| No ad-hoc parsing in apps/runtime | **FAIL** | Runtime зависит от NRes/mission/terrain/prototype; viewer вызывает decoders напрямую | Только AssetManager/mission loader ports; lint dependency denylist | -| Deterministic graph order/IDs | **PARTIAL/UNVERIFIED** | Некоторые sorted/BTree structures и stable hasher есть | Canonical traversal spec, golden graph serialization, cross-run/cross-OS tests | -| Licensed Part 1/2 zero-failure reachability | **UNVERIFIED, EXIT BLOCKER** | Нет доступных run artifacts; inspected game corpus test помечен `#[ignore]` | Full mission matrix reports, expected counts, zero unexplained failures | - -## 6.2 Критические замечания Stage 2 - -### S2-B01 — `fparkan-prototype` и `fparkan-assets` нарушают целевое разделение ответственности - -**Приоритет:** BLOCKER -**Файлы:** `crates/fparkan-prototype/Cargo.toml`, `crates/fparkan-assets/Cargo.toml` - -Prototype crate зависит от: - -- material; -- MSH; -- NRes; -- Texm; -- resource/VFS. - -И сам расширяет graph report visual dependencies. Затем `fparkan-assets` повторно декодирует MSH, WEAR, MAT0 и Texm. Возникают два preparation paths, которые со временем неизбежно разойдутся по fallback, diagnostics и budgets. - -**Целевой DAG:** - -```text -mission/prototype formats ──> prototype graph (keys + provenance only) - │ - v -resource repository ─────────> fparkan-assets (all CPU decoding/preparation) - │ - v -runtime/world ────────────────> immutable MissionAssets - │ - v -render bridge ────────────────> backend-neutral draw items -``` - -Prototype layer не должен декодировать render assets. - -### S2-B02 — Runtime не использует `fparkan-assets` - -**Приоритет:** BLOCKER -**Файлы:** `crates/fparkan-runtime/Cargo.toml`, `src/lib.rs` - -Runtime manifest не содержит dependency на `fparkan-assets`, зато напрямую зависит от NRes, mission-format, prototype, terrain-format и resource. `LoadedMissionState` хранит `Vec<EffectivePrototype>`, а не prepared immutable assets. - -Это прямо противоречит exit gate: «runtime получает prepared assets только через asset manager». - -### S2-B03 — Apps продолжают ad-hoc parsing и synthetic resource mapping - -**Приоритет:** BLOCKER - -- Viewer напрямую вызывает NRes/MSH/Texm/terrain decoders. -- Game создаёт `GpuMeshId(slot + 1)`, constant material ID и triangle range, вместо prepared mission assets. -- Ни game, ни viewer не подключают platform/Vulkan adapters. - -Apps должны быть composition roots, а не дополнительным parser/service layer. - -### S2-B04 — `PrototypeGraph` не является materialized dependency graph - -**Приоритет:** BLOCKER -**Файл:** `crates/fparkan-prototype/src/lib.rs` - -Текущая структура содержит только: - -- roots; -- flattened prototype requests. - -Report содержит counts и failures, но не даёт: - -- stable node identity; -- конкретные edge instances; -- parent/child traversal; -- deduplication semantics; -- source spans; -- full provenance chain; -- serialization для golden comparison. - -Для Stage 2 нужен arena/adjacency graph, а report должен вычисляться из него, а не заменять его. - -### S2-B05 — Публичный resolver теряет multi-component unit - -**Приоритет:** BLOCKER/HIGH - -`resolve_prototype()` для DAT вызывает helper, возвращающий первый resolved component. Хотя другой internal path обходит все records, наличие публичного lossy API нарушает invariant Stage 2 и создаёт риск использования «первого visual» в новом caller. - -**Рекомендация:** удалить этот API либо переименовать в явно lossy diagnostic helper; основной API всегда возвращает collection/subgraph. - -### S2-H01 — Missing unit DAT может быть преобразован в пустое успешное expansion - -**Приоритет:** HIGH, требует подтверждающего regression test - -В inspected helper `VfsError::NotFound` превращается в `Ok` с `expected_count = 0` и пустым списком. Если caller не создаёт отдельный failure до/после этого вызова, reachable missing dependency исчезает из failure set. - -Требуемое поведение: - -- reachable + required → typed failure; -- unreachable → warning/report record; -- optional → explicit fallback edge; -- corrupt → error независимо от optionality, если ресурс найден и malformed. - -### S2-H02 — Provenance enum недостаточен и не покрывает весь канонический список - -**Приоритет:** HIGH - -Текущий enum описывает несколько типов переходов, но отсутствуют или не материализованы: - -- prototype inheritance parent; -- BASE/resource variant; -- component hierarchy/link; -- effect/FX dependency; -- fallback source; -- source archive entry/span; -- exact mission object identity. - -`message: String` не заменяет provenance. - -### S2-H03 — `PreparedVisual` является summary, а не prepared asset - -**Приоритет:** HIGH - -Структура содержит ResourceKey и counts (`model_nodes`, `material_count`, …), но не immutable vertex/index streams, materials, decoded texture mip data, lightmap bindings или dependency handles. Она пригодна для audit report, но не как sole CPU asset handoff в renderer/runtime. - -Нужно разделить: - -- `PreparedVisualSummary` для отчётов; -- `MeshAsset`; -- `MaterialAsset`; -- `TextureAsset`; -- `LightmapAsset`; -- `EffectAsset`; -- `VisualAsset` с typed IDs/handles; -- `MissionAssets` как транзакционно подготовленный набор. - -### S2-H04 — Stable ID без collision handling - -**Приоритет:** HIGH/MEDIUM - -64-bit FNV-like hash удобен и детерминирован, но collision не проверяется. Stable ID — часть persistent captures и graph comparison, поэтому молчаливая коллизия недопустима. - -**Рекомендация:** canonical key interner с equality check; ID может быть SHA-256 prefix/128-bit hash либо deterministic ordinal после canonical sort. При hash collision должна возникать explicit error. - -### S2-H05 — Errors теряют concrete causes - -**Приоритет:** HIGH - -Prototype и assets преобразуют Resource/MSH/Material/Texture errors в строки. В результате graph failure не может отличить missing archive, missing entry, malformed offsets, unsupported variant и allocation limit. - -Это одновременно блокирует Stage 1 diagnostics и Stage 2 optional/corrupt policy. - -### S2-M01 — Graph success predicate слишком зависим от aggregate counts - -**Приоритет:** MEDIUM/HIGH - -`PrototypeGraphReport::is_success()` опирается на отсутствие failures и соответствие aggregate resolved count. Такой predicate не доказывает, что: - -- каждый material/texture/lightmap/effect request resolved; -- каждый edge имеет provenance; -- отсутствуют orphan/duplicate nodes; -- requiredness policy применена; -- graph deterministic. - -Success должен вычисляться как validation materialized graph с инвариантами. - -## 6.3 Положительные элементы Stage 2 - -- Unit DAT records сохраняют raw archive/resource/description bytes и parent/link field. -- Есть отдельный binding variant и CP1251-related support. -- Internal expansion обходит несколько component records. -- Есть depth limit для prototype inheritance. -- Report считает mesh, WEAR, material, texture и lightmap requests/resolutions. -- `AssetId<T>` typed на уровне Rust. -- Asset preparation выполняется транзакционно на уровне метода: ошибка прерывает план. -- Stable ordering поддерживается рядом BTree collections и sorted traversal. -- Mission loading имеет staged phases и transactional world registration concepts. - -## 6.4 Обязательные изменения для полного закрытия Stage 2 - -1. Сделать prototype crate graph-only и убрать из него MSH/material/Texm decoding. -2. Создать materialized typed graph с stable nodes/edge instances. -3. Добавить full provenance и typed requiredness/fallback. -4. Завершить variants: direct, inherited, BASE, unit component hierarchy, effects. -5. Превратить `fparkan-assets` в единственный decode/preparation layer. -6. Создать реальные immutable CPU asset types. -7. Подключить AssetManager к runtime и удалить direct parser dependencies. -8. Перевести apps на runtime/asset services. -9. Ввести deterministic ID registry с collision detection. -10. Запустить полный Part 1/2 mission reachability gate и зафиксировать zero failures. - ---- - -# 7. Сквозные архитектурные замечания - -## 7.1 Нужен единый dependency policy, проверяемый Cargo metadata - -Текущая архитектура допускает запрещённые направления зависимостей. Следует формально проверить: - -- `apps/*` не зависят от format parsers; -- `runtime` не зависит от NRes/RsLi/MSH/Texm/material/FX format crates напрямую; -- `prototype` не зависит от visual asset parsers; -- neutral crates не зависят от `ash`, `winit`, raw OS APIs; -- `headless` dependency closure не содержит platform/Vulkan crates; -- Vulkan raw types не выходят из adapter. - -Policy должна использовать `cargo_metadata`, а не поиск строк в TOML. - -## 7.2 Один error taxonomy для Stage 1–2 - -Рекомендуемый общий набор классификаций: - -```text -MissingArchive -MissingEntry -AmbiguousName -InvalidPath -UnsupportedVariant -CorruptHeader -OutOfBounds -LimitExceeded -AllocationBudgetExceeded -DecompressionFailed -IntegrityMismatch -OptionalUnavailable -FallbackApplied -PlatformUnavailable -GpuCapabilityMissing -``` - -Concrete format errors остаются source; верхние layers добавляют context, не превращая source в строку. - -## 7.3 Acceptance report должен быть доказательством, а не count dump - -Каждый stage report должен включать: - -- schema version; -- engine commit SHA; -- toolchain version; -- platform/target; -- corpus manifest fingerprint; -- configuration/profile; -- counts; -- warnings/failures с stable codes; -- evidence status; -- SHA-256 самого report; -- ссылки на dependent artifacts. - -## 7.4 Документация и код должны иметь один source of truth - -Сейчас canonical Vulkan revision находится в Notion, а repository tome и parity docs содержат прежние stages/commands. Рекомендуется: - -- хранить canonical acceptance schema в repository; -- либо экспортировать Notion plan в versioned Markdown; -- CI должен проверять, что workspace adapter names и documented architecture совпадают; -- устаревшие команды должны быть executable doctests или удалены. - ---- - -# 8. План полного закрытия Stage 0–2 - -Ниже приведена рекомендуемая последовательность PR/changesets. Порядок важен: следующий блок не должен объявляться закрытым до прохождения exit gate предыдущего. - -## 8.1 Stage 0 closure train - -### PR S0-01 — Reproducible toolchain и repository metadata - -**Изменения** - -- закрепить exact Rust toolchain; -- добавить `workspace.package.rust-version`; -- документировать supported targets; -- добавить command `xtask doctor` с выводом toolchain/target/SDK versions; -- report всегда включает commit SHA. - -**Acceptance** - -- clean checkout воспроизводит одинаковый metadata report; -- MSRV job собирает neutral crates; -- current pinned toolchain выполняет полный gate. - -### PR S0-02 — Typed xtask configuration и policy engine - -**Изменения** - -- serde/TOML schema для corpus и acceptance manifests; -- `deny_unknown_fields`; -- canonical absolute path rules для licensed manifest; -- `cargo_metadata` для dependency graph/policy; -- tests malformed/duplicate/unknown/missing fields; -- удалить ручной line parser. - -**Acceptance** - -- malformed manifest не принимается частично; -- unknown fields fail; -- dependency policy работает по package IDs и targets/features. - -### PR S0-03 — Полный synthetic CI gate - -**Обязательные команды** - -```text -cargo fmt --all -- --check -cargo test --workspace --all-targets --all-features --locked -cargo clippy --workspace --all-targets --all-features --locked -- -D warnings -RUSTDOCFLAGS="-D warnings -D rustdoc::broken_intra_doc_links" cargo doc --workspace --no-deps --all-features --locked -cargo deny check advisories bans licenses sources -cargo xtask policy -cargo xtask acceptance audit --strict -``` - -Добавить denylist legacy adapter names, Python runtime files и unsafe allowlist. - -### PR S0-04 — Redesign `fparkan-platform` - -**Изменения** - -- удалить GL context types; -- убрать `present()` из WindowPort; -- ввести normalized events; -- выделить physical/logical size и scale factor; -- определить lifecycle state machine; -- error context/cause. - -**Synthetic tests** - -- resize coalescing; -- scale-factor change; -- minimized zero-size; -- suspend/resume; -- keyboard repeat/modifiers; -- focus loss clears held input; -- deterministic event ordering. - -### PR S0-05 — `fparkan-platform-winit` - -**Изменения** - -- winit event loop и window; -- raw window/display handles; -- platform-specific lifecycle mapping; -- no GPU ownership. - -**Acceptance** - -- window-only smoke на трёх OS; -- event trace соответствует synthetic model. - -### PR S0-06 — Vulkan low-level boundary - -**Изменения** - -- `ash`/`ash-window` adapter; -- loader/instance/debug messenger; -- physical device records и pure scoring; -- queue selection; -- deterministic capability JSON; -- narrow unsafe module policy. - -**Negative tests** - -- no loader; -- no Vulkan 1.1; -- no graphics queue; -- no present queue; -- missing swapchain extension; -- unsupported required format. - -### PR S0-07 — Swapchain, pipeline и offline shaders - -**Изменения** - -- surface/swapchain; -- color/depth policy; -- render pass; -- indexed triangle; -- semaphores/fences; -- frames-in-flight; -- resize/out-of-date/suboptimal; -- offline SPIR-V compile/validate; -- descriptor/push-constant manifest и hashes. - -### PR S0-08 — macOS portability и packaging proof - -**Изменения** - -- enumerate portability flag; -- detect/enable portability subset; -- MoltenVK bundling strategy; -- deterministic portability report; -- `.app` smoke packaging. - -### PR S0-09 — Composition roots и legacy removal - -**Изменения** - -- game/viewer подключают winit+Vulkan adapters; -- headless остаётся isolated; -- удалить SDL/GL stubs; -- очистить lockfile/policy/docs; -- переименовать CPU IDs в neutral render model. - -### PR S0-10 — Native acceptance matrix - -**Jobs** - -- Windows MSVC + Vulkan runtime; -- Linux X11/Wayland smoke; software Vulkan может быть PR gate, отдельный native-GPU job — release gate; -- macOS Apple Silicon + MoltenVK; -- 300 frames, resize, validation error count = 0; -- capability/shader/validation logs как artifacts. - -**Stage 0 закрывается только после merge всех PR S0-01…S0-10 и зелёных platform artifacts.** - ---- - -## 8.2 Stage 1 closure train - -### PR S1-01 — Unified decode context - -- `DecodeLimits` с per-format overrides; -- thread-safe cumulative `AllocationBudget`; -- reservation guards; -- output budget для decompressors; -- span-aware errors; -- migrate all decoders. - -### PR S1-02 — Byte-first paths и host adapter - -- `LegacyPath(Vec<u8>)`; -- ASCII normalization на bytes; -- optional decoded display; -- Unix `OsStr` bytes path; -- Windows conversion policy с explicit encoding/error; -- strict/compatible contract table. - -### PR S1-03 — VFS hardening - -- capability/openat-style traversal; -- no-follow final open; -- root confinement; -- visited file identity при allowed symlinks; -- segment-safe prefix semantics; -- strict fingerprint mode; -- одинаковый casefold policy во всех implementations. - -### PR S1-04 — Diagnostics integration - -- serde-backed diagnostic schema; -- stable codes; -- source chain; -- archive/entry/span/phase context; -- adapters для all format errors; -- property tests JSON; -- запрет `source.to_string()` в domain errors через policy/lint review. - -### PR S1-05 — NRes finalization - -- decode context integration; -- edit preservation tests; -- stable directory order specification; -- malformed/fuzz corpus; -- Part 1/2 no-edit byte identity. - -### PR S1-06 — RsLi editable/lossless model - -- parsed/preserved/editable segments; -- deterministic table/lookup rebuild; -- packing policy; -- all observed methods with budgets; -- AO/EOF+1/presort quirk evidence registry; -- no-edit and edited roundtrips. - -### PR S1-07 — Resource repository finalization - -- typed source errors; -- cache/decode budgets separated; -- deterministic LRU policy documented; -- concurrent same-entry decode coalescing или explicit duplicate policy; -- stale handle/concurrency tests; -- strict verification mode. - -### PR S1-08 — Production corpus runner - -- parser registry, а не extension counters; -- NRes, RsLi и все Stage 1-relevant production parsers; -- any parser error increments failures и causes non-zero exit; -- stable schema and diff; -- no licensed path leakage in synthetic artifacts. - -### PR S1-09 — Licensed closure artifacts - -Для Part 1 и Part 2 отдельно: - -- corpus manifest SHA; -- archive inventory; -- parser report; -- method/quirk coverage; -- no-edit roundtrip report; -- edit-preservation regression set; -- unexplained failures = 0. - -**Stage 1 закрывается только после S1-01…S1-09 и двух стабильных licensed reports.** - ---- - -## 8.3 Stage 2 closure train - -### PR S2-01 — Typed graph schema - -Ввести: - -```text -NodeId -NodeKind { MissionObject, UnitDat, UnitComponent, Prototype, Model, - Wear, Material, Texture, Lightmap, Effect, Auxiliary } -EdgeId -EdgeKind -Requiredness { Required, Optional, Fallback } -Provenance { source path/archive/entry/span, parent edge, object/component indices } -DependencyGraph { nodes, edges, roots } -``` - -Graph validation проверяет no dangling edges, canonical order, unique node keys и parent chain. - -### PR S2-02 — Prototype resolver variants - -- direct registry; -- inherited parent chain; -- BASE/resource variants; -- unit DAT binding; -- multi-component unit hierarchy; -- cycle/depth diagnostics; -- lossless raw fields; -- remove first-component public API. - -### PR S2-03 — Requiredness и failure semantics - -- reachable required missing = error; -- unreachable missing = warning; -- optional missing = explicit optional record; -- fallback = edge с причиной и chosen source; -- found-but-corrupt = error; -- stable diagnostic codes and full chain. - -### PR S2-04 — Разделение prototype/assets - -- убрать MSH/material/Texm dependencies из prototype; -- graph хранит только resource identities/provenance; -- `fparkan-assets` единолично вызывает visual/effect parsers; -- policy запрещает обратные dependencies. - -### PR S2-05 — Immutable CPU assets и ID registry - -- typed immutable model/material/texture/lightmap/effect assets; -- canonical IDs; -- collision detection; -- deduplication; -- source provenance; -- separate parsed-byte and resident-asset budgets. - -### PR S2-06 — Mission AssetManager transaction - -`AssetManager::prepare_mission(graph)`: - -1. валидирует graph; -2. вычисляет canonical load plan; -3. декодирует resources в budget; -4. собирает immutable assets; -5. не публикует partial result при failure; -6. возвращает `MissionAssets` + report. - -### PR S2-07 — Runtime integration - -- runtime зависит от assets API; -- удалить direct NRes/MSH/Texm/material/effect parsing из runtime; -- `LoadedMissionState` хранит `Arc<MissionAssets>`; -- world objects ссылаются на typed asset IDs; -- render snapshot строится из prepared visuals, а не из object slot. - -### PR S2-08 — App cleanup - -- viewer использует AssetManager service; -- CLI parser-level commands остаются только в dedicated tooling crate; -- game не генерирует synthetic GPU IDs; -- dependency policy запрещает app → format crates. - -### PR S2-09 — Synthetic graph suite - -Обязательные fixtures: - -- direct prototype; -- inherited prototype; -- multi-level inheritance; -- cycle; -- depth limit; -- BASE variant; -- multi-component unit с hierarchy; -- duplicate/casefold ambiguity; -- required missing; -- optional missing; -- corrupt present resource; -- material/texture/lightmap/effect chain; -- stable serialization/IDs на повторном запуске. - -### PR S2-10 — Licensed reachability closure - -Для каждой миссии обеих частей: - -- roots; -- unit components; -- prototype/model/material/texture/lightmap/effect nodes; -- required/optional/fallback counts; -- failure list; -- canonical graph hash; -- asset plan hash. - -Exit conditions: - -- reachable failures = 0; -- каждый resolved edge имеет provenance; -- повторный запуск даёт те же graph/asset hashes; -- runtime parser dependency audit = clean. - -**Stage 2 закрывается только после S2-01…S2-10 и полного licensed mission matrix.** - ---- - -# 9. Требуемая CI/acceptance модель - -## 9.1 Synthetic PR gate - -Должен работать без игровых каталогов и без silent skip: - -1. formatting/lints/docs/security; -2. all unit/integration/property tests; -3. malformed parser corpus; -4. Stage 0 pure policy tests; -5. Stage 1 archive synthetic roundtrips; -6. Stage 2 graph synthetic fixtures; -7. headless dependency assertion; -8. Linux software-Vulkan smoke при доступности; -9. report schema validation. - -Любой test, требующий licensed files, должен быть в отдельном command suite, а не `#[ignore]` внутри общего gate без machine-readable explanation. - -## 9.2 Native platform gate Stage 0 - -| Platform | Минимальный gate | Дополнительное доказательство | -|---|---|---| -| Windows | system Vulkan loader, real swapchain, triangle, resize, 300 frames, validation=0 | NVIDIA/AMD/Intel coverage по release cadence | -| Linux | X11 или Wayland surface, swapchain, resize, validation=0 | software Vulkan PR job + native Mesa/NVIDIA release jobs | -| macOS | MoltenVK, portability enumeration/subset, CAMetalLayer surface, resize, validation=0 | Apple Silicon primary; Intel optional only if declared supported | - -## 9.3 Licensed local/restricted gate - -- absolute roots только из local manifest; -- CI logs не содержат raw licensed paths; -- reports используют logical corpus IDs; -- artifacts не содержат game bytes; -- manifests содержат только path hashes/size/format metrics; -- failures приводят к non-zero exit; -- baseline update требует reviewed diff и reason. - ---- - -# 10. Definition of Done - -## 10.1 Stage 0 DoD - -- [ ] Exact Rust toolchain и MSRV закреплены. -- [ ] Full fmt/test/clippy/doc/security/source/license gate проходит. -- [ ] Typed manifests и `cargo_metadata` используются. -- [ ] Windows/Linux/macOS matrix сохраняет artifacts. -- [ ] `fparkan-platform-winit` реализован. -- [ ] `fparkan-render-vulkan` реализован. -- [ ] Vulkan 1.1 baseline и capability report реализованы. -- [ ] MoltenVK portability path реализован. -- [ ] Offline SPIR-V validation/hash manifest реализован. -- [ ] Legacy SDL/GL adapters и references удалены. -- [ ] Game/viewer используют новые composition adapters. -- [ ] 300-frame + resize smoke проходит на трёх OS без validation errors. -- [ ] Headless dependency closure не содержит window/Vulkan. - -## 10.2 Stage 1 DoD - -- [ ] Path identity byte-first и roundtrip-safe. -- [ ] VFS root/symlink/casefold semantics едины и безопасны. -- [ ] Все decoders принимают общий limits/budget context. -- [ ] Decompression output bounded до allocation. -- [ ] NRes no-edit и edited roundtrips подтверждены. -- [ ] RsLi no-edit и edited roundtrips подтверждены. -- [ ] All observed RsLi methods/quirks имеют evidence records. -- [ ] Structured diagnostics проходят через parsers/repository/runtime. -- [ ] JSON reports валидны для всех Unicode/control inputs. -- [ ] Resource repository сохраняет typed errors, handles и deterministic eviction. -- [ ] Corpus report вызывает production parsers. -- [ ] Part 1/2 reports стабильны; unexplained failures = 0. - -## 10.3 Stage 2 DoD - -- [ ] Materialized typed dependency graph существует. -- [ ] Все edge instances имеют full provenance. -- [ ] Direct/inherited/BASE/unit hierarchy variants реализованы. -- [ ] Все unit components регистрируются; first-component shortcut отсутствует. -- [ ] Effect dependencies включены. -- [ ] Required/optional/fallback/corrupt semantics разделены. -- [ ] `fparkan-assets` — единственный CPU preparation layer. -- [ ] Apps/runtime не зависят от format parsers напрямую. -- [ ] Immutable CPU assets и collision-safe stable IDs реализованы. -- [ ] Runtime хранит/использует `MissionAssets`. -- [ ] Render snapshots используют prepared asset IDs. -- [ ] Synthetic graph fixtures проходят. -- [ ] Все миссии Part 1/2 дают reachable failures = 0. -- [ ] Graph/asset hashes детерминированы. - ---- - -# 11. Рекомендуемые automated policy checks - -Добавить в `cargo xtask policy`: - -1. Запрет workspace members/path names: - - `fparkan-platform-sdl`; - - `fparkan-render-gl`; - - stale OpenGL/GLES profile symbols. -2. Dependency rules: - - apps не зависят от `fparkan-*format`, NRes, RsLi, MSH, Texm, material, FX; - - runtime не зависит от этих crates напрямую; - - prototype не зависит от MSH/material/Texm/FX; - - headless не зависит от winit/ash/ash-window/Vulkan adapter; - - neutral crates не зависят от platform adapters. -3. Unsafe rules: - - unsafe разрешён только в exact Vulkan/FFI modules; - - каждый block содержит `SAFETY:`; - - raw handles не являются public API. -4. Test rules: - - synthetic gate не содержит licensed roots; - - ignored tests должны иметь registered reason и отдельный suite owner; - - acceptance IDs уникальны. -5. Documentation rules: - - documented crates/commands существуют; - - stage schema version совпадает с report schema; - - old backend names отсутствуют в canonical docs. - ---- - -# 12. Риски реализации и способы снижения - -| Риск | Влияние | Снижение | -|---|---|---| -| Vulkan work начнётся до исправления platform contract | Повторная переделка surface/present/lifecycle | Сначала S0-04, затем adapters | -| Global unsafe prohibition будет ослаблен целиком | Рост FFI risk | Изолированный audited crate/module и policy scanner | -| Stage 1 budgets добавят только к top-level files | Nested decompression bomb останется | Общий shared budget, передаваемый во все вложенные decoders | -| RsLi writer будет canonical-only | Потеря unknown/overlay bytes | Segment-preserving editable model и lossless-first tests | -| Graph report останется count-only | False green Stage 2 | Materialized graph + invariant validator + canonical serialization | -| Prototype и assets продолжат оба парсить visuals | Divergent fallback и diagnostics | Жёсткий dependency DAG и policy test | -| Hash IDs столкнутся | Неправильные assets/captures | Collision detection и canonical interner | -| Licensed tests останутся ignored/local-only без artifacts | Невозможность доказать exit gate | Separate command, signed reports, baseline diff process | -| GitHub mirror и primary diverge | Audit не соответствует release | Pin canonical remote + commit SHA в каждом report | -| Documentation останется отдельной от acceptance schema | Повторное рассогласование stages | Versioned repository schema и generated docs/checks | - ---- - -# 13. Приоритетный backlog - -## Немедленно — до любых новых gameplay/render features - -1. S0-01…S0-04: reproducibility, CI, typed config, platform API. -2. S1-01: общий decode/allocation budget. -3. S2-04: запрет дублирующего parsing между prototype/assets. -4. S1-04: typed diagnostics без string erasure. - -## Затем — минимальный доказуемый Vulkan foundation - -1. winit adapter. -2. Vulkan loader/device/surface/swapchain. -3. offline shaders. -4. 3-platform smokes. -5. legacy adapter removal. - -## Затем — архивный exit gate - -1. byte-first paths/VFS hardening; -2. RsLi edit/writer; -3. production corpus runner; -4. licensed reports/roundtrips. - -## Затем — graph/assets exit gate - -1. typed graph/provenance; -2. all variants/components/effects; -3. immutable assets; -4. runtime/app integration; -5. full Part 1/2 reachability. - ---- - -# 14. Реестр доказательств - -## Canonical requirements - -- Notion, Vulkan revision: <https://app.notion.com/p/387e79f2db3981778f94cdf34db5f93f> - -## Workspace/governance - -- Root manifest: <https://github.com/valentineus/fparkan/blob/devel/Cargo.toml> -- Toolchain: <https://github.com/valentineus/fparkan/blob/devel/rust-toolchain.toml> -- Cargo config: <https://github.com/valentineus/fparkan/blob/devel/.cargo/config.toml> -- xtask manifest: <https://github.com/valentineus/fparkan/blob/devel/xtask/Cargo.toml> -- xtask implementation: <https://github.com/valentineus/fparkan/blob/devel/xtask/src/main.rs> -- README: <https://github.com/valentineus/fparkan/blob/devel/README.md> - -## Stage 0 - -- Platform core: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-platform/src/lib.rs> -- SDL stub adapter: <https://github.com/valentineus/fparkan/blob/devel/adapters/fparkan-platform-sdl/src/lib.rs> -- Render core: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-render/src/lib.rs> -- GL stub adapter: <https://github.com/valentineus/fparkan/blob/devel/adapters/fparkan-render-gl/src/lib.rs> -- Game composition: <https://github.com/valentineus/fparkan/blob/devel/apps/fparkan-game/src/main.rs> -- Viewer composition: <https://github.com/valentineus/fparkan/blob/devel/apps/fparkan-viewer/src/main.rs> -- Headless manifest: <https://github.com/valentineus/fparkan/blob/devel/apps/fparkan-headless/Cargo.toml> - -## Stage 1 - -- Binary/limits: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-binary/src/lib.rs> -- Paths: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-path/src/lib.rs> -- VFS: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-vfs/src/lib.rs> -- NRes: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-nres/src/lib.rs> -- RsLi: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-rsli/src/lib.rs> -- Diagnostics: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-diagnostics/src/lib.rs> -- Resource repository: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-resource/src/lib.rs> -- Corpus runner: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-corpus/src/lib.rs> - -## Stage 2 - -- Prototype graph: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-prototype/src/lib.rs> -- Prototype manifest: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-prototype/Cargo.toml> -- Assets: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-assets/src/lib.rs> -- Assets manifest: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-assets/Cargo.toml> -- Mission format: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-mission-format/src/lib.rs> -- Runtime: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-runtime/src/lib.rs> -- Runtime manifest: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-runtime/Cargo.toml> -- FX manifest: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-fx/Cargo.toml> - -## Documentation drift - -- Repository implementation tome: <https://github.com/valentineus/fparkan/blob/devel/docs/tomes/07-implementation.md> -- Parity README: <https://github.com/valentineus/fparkan/blob/devel/parity/README.md> -- Parity cases: <https://github.com/valentineus/fparkan/blob/devel/parity/cases.toml> - ---- - -# 15. Финальное заключение - -Проект не находится в плохом состоянии: у него уже есть заметно более сильная CPU/data foundation, чем обычно бывает на ранней стадии восстановления движка. Однако текущая структура создаёт риск преждевременного объявления stages завершёнными, потому что stubs, count reports и ignored licensed tests могут выглядеть как acceptance evidence. - -Главный принцип закрытия: - -> Stage считается завершённым не тогда, когда существует crate или test с нужным названием, а когда канонический exit gate выполняется на production path, выдаёт воспроизводимый machine-readable artifact и не имеет обходного альтернативного пути. - -Для Stage 0 production path — настоящий Vulkan swapchain на трёх OS. -Для Stage 1 — bounded production parsers плюс lossless licensed roundtrips. -Для Stage 2 — materialized typed graph, immutable assets и runtime, который не обходит AssetManager. - -До выполнения перечисленного рекомендуется маркировать текущий статус как: - -```text -Stage 0: IN PROGRESS / BLOCKED -Stage 1: IN PROGRESS / ARCHIVE FOUNDATION PARTIAL -Stage 2: IN PROGRESS / GRAPH AND ASSET ARCHITECTURE NOT CLOSED -``` diff --git a/fparkan_stage_0_audit_2026-06-23.md b/fparkan_stage_0_audit_2026-06-23.md new file mode 100644 index 0000000..979c2cf --- /dev/null +++ b/fparkan_stage_0_audit_2026-06-23.md @@ -0,0 +1,643 @@ +# FParkan — аудит Stage 0 и план полного закрытия + +**Проект:** `valentineus/fparkan` +**Проверенная ветка:** `devel` GitHub-зеркала +**Дата аудита:** 23 июня 2026 года +**Область:** только Stage 0 — Governance, reproducibility и Vulkan foundation +**Метод:** статический архитектурный и кодовый аудит +**Сборка и исполнение:** не выполнялись; `cargo build`, `cargo test`, Vulkan smoke и validation jobs не запускались + +--- + +## 1. Итоговый вердикт + +**Stage 0 не закрыт и находится в статусе `BLOCKED`.** + +Главный критерий Stage 0 — воспроизводимый репозиторий и минимальный настоящий Vulkan vertical slice на Windows, Linux и macOS. В проверенном состоянии: + +- отсутствует `fparkan-platform-winit`; +- отсутствует `fparkan-render-vulkan`; +- отсутствуют Vulkan instance/device/surface/swapchain; +- `fparkan-game` использует `RecordingBackend`, а не GPU backend; +- workspace по-прежнему содержит SDL/OpenGL stub adapters; +- Rust toolchain закреплён только как изменяемый канал `stable`; +- `cargo xtask ci` не реализует полный канонический gate; +- нет подтверждённых артефактов Windows/Linux/macOS smoke jobs. + +### Сводная оценка + +| Группа требований | Статус | Основной блокер | +|---|---|---| +| Reproducibility и toolchain | **FAIL** | Toolchain не закреплён точной версией, MSRV не объявлен | +| Repository policy и CI | **FAIL** | Неполные fmt/test/clippy/doc/security gates | +| Platform abstraction | **FAIL** | Core API содержит OpenGL-specific contract; `winit` adapter отсутствует | +| Vulkan backend | **FAIL** | Нет Vulkan loader/device/surface/swapchain/pipeline | +| macOS portability | **FAIL** | Нет MoltenVK integration и portability handling | +| Offline shaders | **FAIL** | Нет SPIR-V build/validation/hash pipeline | +| Legacy cleanup | **FAIL** | SDL/GL stubs остаются workspace members | +| Headless isolation | **PASS на manifest-level** | Автоматическое доказательство dependency closure ещё требуется | +| Native acceptance | **FAIL / NOT RUNNABLE** | Нет реального backend и platform artifacts | + +Stage 0 можно объявить закрытым только после прохождения реального Vulkan smoke на всех трёх системах и публикации machine-readable артефактов. + +--- + +## 2. Область и ограничения аудита + +Канонические требования взяты из документа: + +- «План реализации stage 0–5: Vulkan revision»; +- <https://app.notion.com/p/387e79f2db3981778f94cdf34db5f93f>. + +Проверялась ветка: + +- <https://github.com/valentineus/fparkan/tree/devel>. + +Ограничения: + +1. Ветка `devel` является движущейся ссылкой. Следующий formal audit следует выполнять на закреплённом commit SHA или tag. +2. README указывает self-hosted repository как primary. Его закрытые CI runners и artifacts не были доступны. +3. Код не собирался и не запускался по условию аудита. +4. Vulkan runtime, validation layers, MoltenVK и native window creation не проверялись динамически. +5. Статический анализ достаточен для определения текущих архитектурных блокеров: требуемых adapters и зависимостей в workspace нет. + +--- + +## 3. Матрица требований Stage 0 + +| Требование | Статус | Текущее состояние | Необходимо для закрытия | +|---|---|---|---| +| Exact stable Rust toolchain | **FAIL** | `rust-toolchain.toml`: `channel = "stable"` | Закрепить точную версию, например `1.xx.y` | +| Объявленный MSRV | **FAIL** | `workspace.package.rust-version` отсутствует | Добавить `rust-version` и отдельный MSRV job | +| Полный `cargo xtask ci` | **FAIL** | Есть custom rustfmt, policy, workspace test и clippy | Добавить канонические fmt/test/clippy/doc/security gates | +| `--all-targets --all-features` | **FAIL** | Не используются текущим `ci` | Добавить к test/clippy/doc gates | +| Clippy `-D warnings` | **FAIL** | Явно не передаётся | Сделать предупреждения blocking | +| Rustdoc broken-link gate | **FAIL** | Отсутствует | Добавить `RUSTDOCFLAGS=-D warnings -D rustdoc::broken_intra_doc_links` | +| License/advisory/source policy | **PARTIAL / UNVERIFIED** | Есть custom policy и GPL workspace license | Подключить `cargo-deny` или эквивалент и хранить versioned policy | +| Typed TOML parsing | **FAIL** | Licensed manifest разбирается вручную построчно | `serde` + TOML schema + `deny_unknown_fields` | +| `cargo_metadata` policy | **FAIL** | Dependency rules не опираются на typed Cargo graph | Добавить `cargo_metadata` и package-ID based checks | +| CI matrix Windows/Linux/macOS | **UNVERIFIED / BLOCKER** | Доступных platform artifacts нет | Создать native matrix и сохранять reports | +| Backend-neutral platform API | **FAIL** | В core есть `GraphicsProfile`, GL/GLES versions и `WindowPort::present()` | Удалить GL context concepts; present перенести в renderer | +| `fparkan-platform-winit` | **FAIL** | В workspace только SDL-named stub | Реализовать настоящий event loop/window adapter | +| `fparkan-render-vulkan` | **FAIL** | В workspace только GL-named recording stub | Реализовать настоящий Vulkan backend | +| Vulkan loader/instance/device | **FAIL** | Vulkan bindings отсутствуют | Добавить `ash`, instance, device selection, queues | +| Surface/swapchain/present | **FAIL** | Отсутствуют | Реализовать platform surface и swapchain lifecycle | +| Indexed triangle | **FAIL** | Есть только command capture | Нарисовать реальный indexed triangle | +| Resize/out-of-date/suboptimal | **FAIL** | Swapchain отсутствует | Реализовать полную recreation policy | +| Deterministic capability report | **FAIL** | Device discovery отсутствует | Pure scoring policy + JSON capability report | +| macOS portability | **FAIL** | MoltenVK integration отсутствует | Portability enumeration, subset и packaged MoltenVK | +| Offline SPIR-V pipeline | **FAIL** | GL stub проверяет только synthetic markers | Pinned compiler, validator, descriptor manifest и hashes | +| Legacy adapter removal | **FAIL** | SDL/GL crates входят в workspace | Удалить crates и все references после замены | +| Game/viewer composition | **FAIL** | Game использует `RecordingBackend`; viewer — CLI inspector | Подключить winit + Vulkan только в composition roots | +| Headless isolation | **PASS на manifest-level** | Нет window/Vulkan dependency | Добавить automated Cargo metadata assertion | +| 300 frames + resize + validation=0 | **FAIL** | Невозможно выполнить без backend | Native smoke jobs на трёх OS | +| Negative Vulkan tests | **FAIL** | Нет Vulkan error model | Loader/device/queue/format failure fixtures | + +--- + +## 4. Замечания + +### S0-B01 — Workspace содержит удаляемые SDL/OpenGL stub crates + +**Приоритет:** BLOCKER +**Файлы:** `Cargo.toml`, `adapters/fparkan-platform-sdl`, `adapters/fparkan-render-gl` + +Root workspace включает оба прежних adapter crate. При этом: + +- SDL adapter не зависит от SDL и содержит in-memory stubs; +- GL adapter не зависит от OpenGL и только сохраняет canonical command captures; +- их tests доказывают deterministic stub behavior, а не platform/GPU integration. + +Это создаёт ложноположительный сигнал готовности backend-а. + +**Рекомендация:** + +1. До появления замены пометить crates как `legacy-proof` и исключить из default production composition. +2. Добавить policy, запрещающий приложениям зависеть от них. +3. После подключения `platform-winit` и `render-vulkan` удалить crates, lockfile references, docs и tests. + +### S0-B02 — Core platform contract остаётся OpenGL-specific + +**Приоритет:** BLOCKER +**Файл:** `crates/fparkan-platform/src/lib.rs` + +Проблемы: + +- `GraphicsProfile::DesktopCore/Embedded` описывает GL/GLES profile; +- `GraphicsContextRequest` описывает создание GL context; +- `WindowPort::present()` ошибочно закрепляет presentation за window abstraction; +- `PlatformEvent` содержит только `Quit`; +- отсутствуют resize, scale factor, focus, keyboard, mouse, suspend/resume и raw handles; +- `PlatformError::Backend` не содержит source/context. + +Для Vulkan окно не выполняет present. Surface, swapchain, image acquisition и queue presentation принадлежат render adapter. + +**Рекомендация:** platform crate должен предоставлять только: + +- event/lifecycle model; +- physical и logical size; +- scale factor; +- normalized input; +- raw window/display handles; +- structured platform errors. + +### S0-B03 — Реального Vulkan code path нет + +**Приоритет:** BLOCKER + +В inspected manifests отсутствуют `ash`, `ash-window`, `winit` и `raw-window-handle`. Следовательно, текущий код не может создать Vulkan instance/device/surface/swapchain. + +`fparkan-game` выполняет backend-neutral capture через `RecordingBackend`. Это полезный CPU oracle, но не Vulkan renderer. + +**Definition of fixed:** отдельный smoke executable открывает окно, создаёт Vulkan swapchain, рисует indexed triangle, обрабатывает resize и корректно завершается. + +### S0-B04 — `cargo xtask ci` не соответствует exit gate + +**Приоритет:** BLOCKER +**Файл:** `xtask/src/main.rs` + +Текущий gate не подтверждает: + +- все targets и features; +- clippy с `-D warnings`; +- rustdoc warnings и broken links; +- advisory/source policy; +- dependency denylist; +- отсутствие project-owned unsafe вне разрешённого Vulkan boundary; +- корректность typed acceptance manifests; +- platform-native smoke jobs. + +Custom recursive rustfmt также может расходиться с canonical `cargo fmt --all -- --check`. + +### S0-B05 — Toolchain не воспроизводим + +**Приоритет:** BLOCKER +**Файл:** `rust-toolchain.toml` + +Канал `stable` изменяется. Один и тот же commit может использовать разные компиляторы в разные дни. MSRV также не объявлен. + +**Рекомендация:** + +- закрепить точный Rust release; +- указать `rust-version`; +- обновлять toolchain отдельным reviewed PR; +- сохранять toolchain и SDK versions в acceptance report. + +### S0-H01 — Нужен изолированный audited unsafe boundary + +**Приоритет:** HIGH + +`unsafe_code = "forbid"` правильно сохранять для backend-neutral crates. Однако Vulkan FFI требует локальных unsafe calls. + +Нельзя ослаблять policy всему workspace. + +**Целевая схема:** + +- unsafe разрешён только в `fparkan-render-vulkan` low-level modules; +- `unsafe_op_in_unsafe_fn = deny`; +- каждый block имеет `// SAFETY:` comment; +- ownership/lifetime rules документированы; +- raw Vulkan handles не выходят в public neutral API; +- custom policy scanner проверяет allowlist. + +### S0-H02 — Neutral render IDs не должны быть GPU allocation IDs + +**Приоритет:** HIGH, не блокирует первый hardcoded triangle +**Файл:** `crates/fparkan-render/src/lib.rs` + +`GpuMeshId` и `GpuMaterialId` появляются до существования GPU registry. Это смешивает CPU asset identity и backend-local allocation identity. + +**Рекомендация:** использовать neutral `MeshAssetId`/`MaterialAssetId`; Vulkan adapter должен самостоятельно отображать их на buffers, images и descriptors. + +### S0-M01 — Документация рассогласована с Vulkan revision + +**Приоритет:** MEDIUM + +`docs/tomes/07-implementation.md` сохраняет старую последовательность и multi-backend формулировки. Parity documentation ссылается на отсутствующий workspace crate, а active parity cases не определены. + +**Рекомендация:** один versioned source of truth для stages и автоматическая проверка упомянутых crates, commands и backend names. + +--- + +## 5. Сильные стороны, которые следует сохранить + +- Workspace lint policy строгая и подходит для backend-neutral crates. +- `Cargo.lock` присутствует, а команды используют `--locked`. +- Synthetic и licensed corpus paths концептуально разделены. +- `fparkan-headless` не зависит от platform/render adapters на manifest-level. +- `fparkan-render` уже предоставляет deterministic command ordering, validation и canonical capture. +- Composition roots отделены от большинства core crates. + +Эти элементы позволяют построить Vulkan foundation без переписывания CPU/data foundation. + +--- + +## 6. Целевая архитектура Stage 0 + +```text +apps/fparkan-game, apps/fparkan-viewer + │ + ├── fparkan-platform-winit + │ └── winit + raw-window-handle + │ + └── fparkan-render-vulkan + ├── ash-window + ├── ash + ├── surface / swapchain + ├── device / queues + ├── shaders / pipelines + └── synchronization / presentation + +apps/fparkan-headless + └── runtime/core only + no winit, ash, MoltenVK or window dependencies +``` + +Разделение ответственности: + +- `fparkan-platform`: события, input, lifecycle, sizes и handle access; +- `fparkan-platform-winit`: concrete window/event-loop implementation; +- `fparkan-render`: backend-neutral command/snapshot contracts; +- `fparkan-render-vulkan`: Vulkan resources, synchronization и present; +- game/viewer: composition root; +- headless: полностью изолированный путь. + +--- + +## 7. План полного закрытия Stage 0 + +Порядок PR важен. Vulkan adapter не следует строить поверх текущего GL-oriented platform contract. + +### PR S0-01 — Reproducible toolchain и metadata + +**Изменения** + +- закрепить exact Rust toolchain; +- добавить `workspace.package.rust-version`; +- зафиксировать supported triples; +- добавить `cargo xtask doctor`; +- включать commit SHA, Rust version и platform SDK versions в reports. + +**Acceptance** + +- clean checkout формирует одинаковый metadata report; +- MSRV job собирает backend-neutral crates; +- pinned toolchain проходит полный synthetic gate. + +### PR S0-02 — Typed xtask configuration + +**Изменения** + +- `serde` + TOML schemas для corpus/acceptance manifests; +- `deny_unknown_fields`; +- duplicate/missing/unknown-field validation; +- absolute canonical paths для local licensed manifest; +- `cargo_metadata` для dependency и workspace policy; +- удалить ручной line parser. + +**Acceptance** + +- malformed manifest всегда даёт non-zero exit; +- неизвестные поля не игнорируются; +- dependency policy работает по Cargo package IDs, targets и features. + +### PR S0-03 — Полный synthetic CI gate + +Обязательные команды: + +```bash +cargo fmt --all -- --check +cargo test --workspace --all-targets --all-features --locked +cargo clippy --workspace --all-targets --all-features --locked -- -D warnings +RUSTDOCFLAGS="-D warnings -D rustdoc::broken_intra_doc_links" \ + cargo doc --workspace --no-deps --all-features --locked +cargo deny check advisories bans licenses sources +cargo xtask policy +cargo xtask acceptance audit --strict +``` + +Добавить reports для каждого gate и запрет silent skip. + +### PR S0-04 — Redesign `fparkan-platform` + +**Изменения** + +- удалить `GraphicsProfile`, `GraphicsContextRequest` и GL version negotiation; +- убрать `present()` из window port; +- добавить normalized keyboard/mouse events; +- physical/logical size и scale factor; +- focus, minimize, occlusion, suspend/resume; +- deterministic lifecycle state machine; +- structured errors с source chain. + +**Synthetic tests** + +- resize coalescing; +- zero-size/minimized window; +- scale-factor changes; +- focus loss clears held input; +- key repeat и modifiers; +- suspend/resume; +- deterministic event ordering. + +### PR S0-05 — `fparkan-platform-winit` + +**Изменения** + +- winit event loop; +- native window lifecycle; +- raw window/display handles; +- platform-specific event normalization; +- отсутствие GPU ownership. + +**Acceptance** + +- window-only smoke на Windows, Linux и macOS; +- native event trace соответствует synthetic model. + +### PR S0-06 — Vulkan low-level boundary + +**Изменения** + +- `ash` и `ash-window`; +- dynamic Vulkan loader; +- instance и debug messenger; +- physical device capability records; +- pure deterministic device scoring; +- graphics/present queue selection; +- deterministic capability JSON; +- audited unsafe allowlist. + +**Negative tests** + +- loader отсутствует; +- Vulkan 1.1 недоступен; +- graphics queue отсутствует; +- present queue отсутствует; +- `VK_KHR_swapchain` отсутствует; +- required surface format отсутствует. + +### PR S0-07 — Swapchain, triangle и offline shaders + +**Изменения** + +- surface и swapchain; +- format/present-mode/image-count policy; +- render pass и graphics pipeline; +- indexed triangle; +- command pools/buffers; +- binary semaphores и fences; +- frames in flight; +- resize/out-of-date/suboptimal/zero extent handling; +- pinned offline shader compiler; +- SPIR-V validation; +- descriptor/push-constant manifest; +- shader content hashes. + +### PR S0-08 — macOS portability proof + +**Изменения** + +- `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR`; +- portability extension enumeration; +- `VK_KHR_portability_subset` enablement, если объявлен device; +- MoltenVK packaging strategy; +- deterministic portability report; +- `.app` bundle smoke. + +### PR S0-09 — Composition roots и legacy removal + +**Изменения** + +- game/viewer подключают winit + Vulkan adapters; +- headless остаётся без window/GPU graph; +- удалить SDL/GL stub crates; +- очистить lockfile, policy и docs; +- заменить GPU-named neutral IDs на asset IDs; +- запретить stale backend names automated policy check-ом. + +### PR S0-10 — Native acceptance matrix + +**Jobs** + +- Windows MSVC + system Vulkan loader; +- Linux X11 или Wayland surface; +- macOS Apple Silicon + MoltenVK; +- отдельный software-Vulkan Linux PR job допустим как быстрый gate; +- native GPU jobs остаются release evidence. + +**Обязательный сценарий** + +1. Создать окно. +2. Создать real Vulkan swapchain. +3. Показать indexed triangle. +4. Выполнить не менее 300 frames. +5. Изменить размер окна. +6. Пересоздать swapchain. +7. Корректно завершить event loop. +8. Получить `validation_error_count = 0`. +9. Сохранить capability, shader и validation reports как artifacts. + +**Stage 0 закрывается только после merge S0-01…S0-10 и зелёных native artifacts.** + +--- + +## 8. Требуемая CI/acceptance модель + +### 8.1 Synthetic PR gate + +Должен работать без игровых каталогов и без silent skip: + +1. fmt, clippy, docs, security и policy; +2. все unit/integration tests; +3. platform lifecycle state-machine tests; +4. device scoring tests на synthetic capability records; +5. swapchain policy tests; +6. shader manifest/hash tests; +7. Vulkan negative-path tests без обязательного GPU; +8. headless dependency assertion; +9. report schema validation. + +Tests, требующие native GPU или licensed data, должны иметь отдельные suites и machine-readable ownership/reason, а не оставаться обычными `#[ignore]` без evidence trail. + +### 8.2 Native platform gate + +| Platform | Минимальный gate | Дополнительное evidence | +|---|---|---| +| Windows | system loader, swapchain, triangle, resize, 300 frames, validation=0 | Периодическая NVIDIA/AMD/Intel coverage | +| Linux | X11 или Wayland surface, swapchain, resize, validation=0 | Software Vulkan PR job + Mesa/NVIDIA native release jobs | +| macOS | MoltenVK, portability enumeration/subset, CAMetalLayer surface, resize, validation=0 | Apple Silicon как primary target | + +### 8.3 Формат machine-readable отчёта + +Минимальные поля: + +```json +{ + "schema": 1, + "commit": "<sha>", + "target": "x86_64-pc-windows-msvc", + "rustc": "1.xx.y", + "vulkan_api": "1.1", + "device_name": "...", + "driver": "...", + "portability_subset": false, + "frames": 300, + "resize_count": 1, + "swapchain_recreate_count": 1, + "validation_error_count": 0, + "shader_manifest_hash": "...", + "result": "pass" +} +``` + +--- + +## 9. Definition of Done + +Stage 0 считается закрытым, когда выполнены **все** пункты: + +- [ ] Exact Rust toolchain закреплён. +- [ ] MSRV объявлен и проверяется. +- [ ] Full fmt/test/clippy/doc/security/source/license gate проходит. +- [ ] Typed TOML manifests используются. +- [ ] Dependency policy работает через `cargo_metadata`. +- [ ] Windows/Linux/macOS matrix сохраняет artifacts. +- [ ] `fparkan-platform` больше не содержит GL-specific context concepts. +- [ ] `fparkan-platform-winit` реализован. +- [ ] `fparkan-render-vulkan` реализован. +- [ ] Vulkan 1.1 instance/device/queues/surface/swapchain реализованы. +- [ ] Deterministic device scoring и capability report реализованы. +- [ ] Indexed triangle рисуется настоящим Vulkan backend. +- [ ] Resize, zero extent, out-of-date и suboptimal обработаны. +- [ ] MoltenVK portability path реализован. +- [ ] Offline SPIR-V validation и hash manifest реализованы. +- [ ] Unsafe разрешён только в audited Vulkan/FFI modules. +- [ ] Legacy SDL/GL adapters и references удалены. +- [ ] Game/viewer используют новые composition adapters. +- [ ] Headless dependency graph не содержит winit/Vulkan/MoltenVK. +- [ ] 300-frame + resize smoke проходит на трёх OS. +- [ ] Validation error count равен нулю на трёх OS. +- [ ] Acceptance reports включают commit SHA и сохраняются как artifacts. + +Наличие crates или unit tests с соответствующими названиями само по себе не является закрытием Stage 0. + +--- + +## 10. Рекомендуемые automated policy checks + +Добавить в `cargo xtask policy`: + +### Workspace denylist + +- запрещены `fparkan-platform-sdl` и `fparkan-render-gl` после миграции; +- запрещены stale symbols `GraphicsProfile`, `DesktopCore`, `Embedded`, `Gles2` в canonical platform/render API; +- canonical docs не содержат OpenGL как production backend. + +### Dependency rules + +- headless не зависит от `winit`, `raw-window-handle`, `ash`, `ash-window` или Vulkan adapter; +- backend-neutral crates не зависят от concrete platform/render adapters; +- только composition roots связывают platform и renderer; +- raw Vulkan types не экспортируются из adapter public boundary. + +### Unsafe rules + +- project-owned unsafe разрешён только в exact allowlisted files/modules; +- каждый block содержит `SAFETY:`; +- `unsafe_op_in_unsafe_fn` запрещён; +- изменение allowlist требует отдельного reviewed diff. + +### Test и report rules + +- synthetic gate не получает licensed paths; +- ignored tests обязаны иметь registered reason и owner; +- acceptance IDs уникальны; +- reports проходят schema validation; +- report всегда содержит commit SHA и target triple. + +### Documentation rules + +- документированные crates и commands существуют; +- canonical stage version совпадает с acceptance schema; +- старые backend names отсутствуют; +- README не объявляет незакрытый Vulkan path реализованным. + +--- + +## 11. Основные риски + +| Риск | Последствие | Снижение | +|---|---|---| +| Vulkan adapter начнут до redesign platform API | Повторная переделка surface/lifecycle/present | Сначала S0-04, затем S0-05/S0-06 | +| `unsafe_code` ослабят всему workspace | Рост FFI и lifetime рисков | Изолированный audited adapter и allowlist scanner | +| Stubs будут приняты за production backend | Ложное закрытие Stage 0 | Удаление legacy crates и real native smoke | +| Linux software Vulkan будет единственным evidence | Не выявятся vendor-driver проблемы | Native Mesa/NVIDIA jobs перед release | +| macOS будет проверен без portability subset report | Скрытая несовместимость MoltenVK | Обязательное capability evidence | +| Shader compiler останется неприкреплённым | Невоспроизводимый SPIR-V | Pinned compiler + manifest hashes | +| GitHub mirror и primary repository разойдутся | Audit и release относятся к разному коду | Commit SHA, canonical remote и artifact metadata | +| Документация останется отдельным source of truth | Повторное рассогласование | Versioned stage schema и automated doc checks | + +--- + +## 12. Реестр доказательств + +### Canonical requirement + +- Vulkan revision: <https://app.notion.com/p/387e79f2db3981778f94cdf34db5f93f> + +### Workspace и governance + +- Root manifest: <https://github.com/valentineus/fparkan/blob/devel/Cargo.toml> +- Toolchain: <https://github.com/valentineus/fparkan/blob/devel/rust-toolchain.toml> +- Cargo config: <https://github.com/valentineus/fparkan/blob/devel/.cargo/config.toml> +- xtask manifest: <https://github.com/valentineus/fparkan/blob/devel/xtask/Cargo.toml> +- xtask implementation: <https://github.com/valentineus/fparkan/blob/devel/xtask/src/main.rs> +- README: <https://github.com/valentineus/fparkan/blob/devel/README.md> + +### Platform и render + +- Platform core: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-platform/src/lib.rs> +- SDL stub adapter: <https://github.com/valentineus/fparkan/blob/devel/adapters/fparkan-platform-sdl/src/lib.rs> +- Render core: <https://github.com/valentineus/fparkan/blob/devel/crates/fparkan-render/src/lib.rs> +- GL stub adapter: <https://github.com/valentineus/fparkan/blob/devel/adapters/fparkan-render-gl/src/lib.rs> +- Game composition: <https://github.com/valentineus/fparkan/blob/devel/apps/fparkan-game/src/main.rs> +- Viewer composition: <https://github.com/valentineus/fparkan/blob/devel/apps/fparkan-viewer/src/main.rs> +- Headless manifest: <https://github.com/valentineus/fparkan/blob/devel/apps/fparkan-headless/Cargo.toml> + +### Documentation drift + +- Implementation tome: <https://github.com/valentineus/fparkan/blob/devel/docs/tomes/07-implementation.md> +- Parity README: <https://github.com/valentineus/fparkan/blob/devel/parity/README.md> +- Parity cases: <https://github.com/valentineus/fparkan/blob/devel/parity/cases.toml> + +--- + +## 13. Финальное заключение + +У проекта уже имеется пригодный backend-neutral фундамент: deterministic render commands, строгие neutral-crate lints, отдельный headless composition root и разделение synthetic/licensed tests. Однако Stage 0 пока представлен интерфейсными proof/stub crates, а не настоящим Vulkan vertical slice. + +Критический путь: + +```text +reproducible toolchain + → complete CI/policy gate + → backend-neutral platform redesign + → winit adapter + → Vulkan loader/device/surface/swapchain + → indexed triangle + shaders + synchronization + → MoltenVK portability + → composition integration + → legacy removal + → three-platform acceptance artifacts +``` + +До прохождения этого пути рекомендуемый статус: + +```text +Stage 0: IN PROGRESS / BLOCKED +``` + +Главный критерий закрытия: + +> Stage 0 завершён не тогда, когда существуют crates с названиями `winit` и `vulkan`, а когда один закреплённый commit создаёт настоящий Vulkan swapchain, рисует triangle, переживает resize и завершается без validation errors на Windows, Linux и macOS, сохраняя воспроизводимые machine-readable artifacts. diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 8f67468..2e1f7fb 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,4 +1,23 @@ #![forbid(unsafe_code)] +#![cfg_attr( + test, + allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::expect_used, + clippy::float_cmp, + clippy::identity_op, + clippy::too_many_lines, + clippy::uninlined_format_args, + clippy::map_unwrap_or, + clippy::needless_raw_string_hashes, + clippy::semicolon_if_nothing_returned, + clippy::type_complexity, + clippy::panic, + clippy::unwrap_used + ) +)] #![allow(clippy::print_stderr, clippy::print_stdout)] //! Repository automation for `FParkan`. @@ -175,8 +194,17 @@ fn run_cargo_deny() -> Result<(), String> { fn run_cargo_doc() -> Result<(), String> { let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into()); let status = Command::new(cargo) - .args(["doc", "--workspace", "--all-features", "--locked", "--no-deps"]) - .env("RUSTDOCFLAGS", "-D warnings -D rustdoc::broken_intra_doc_links") + .args([ + "doc", + "--workspace", + "--all-features", + "--locked", + "--no-deps", + ]) + .env( + "RUSTDOCFLAGS", + "-D warnings -D rustdoc::broken_intra_doc_links", + ) .status() .map_err(|err| format!("failed to run cargo doc: {err}"))?; if status.success() { @@ -272,8 +300,10 @@ fn parse_licensed_manifest(path: &Path) -> Result<LicensedCorpusRoots, String> { } let roots = LicensedCorpusRoots { - part1: part1.ok_or_else(|| "licensed manifest is missing part1 corpus entry".to_string())?, - part2: part2.ok_or_else(|| "licensed manifest is missing part2 corpus entry".to_string())?, + part1: part1 + .ok_or_else(|| "licensed manifest is missing part1 corpus entry".to_string())?, + part2: part2 + .ok_or_else(|| "licensed manifest is missing part2 corpus entry".to_string())?, }; validate_licensed_part("part1", &roots.part1)?; validate_licensed_part("part2", &roots.part2)?; @@ -412,16 +442,10 @@ fn validate_cargo_metadata(root: &Path, failures: &mut Vec<String>) -> Result<() } let metadata = MetadataCommand::new() .manifest_path(&manifest) - .no_deps(true) + .no_deps() .other_options(["--offline".to_string(), "--locked".to_string()]) .exec() - .map_err(|error| { - format!( - "{}: cargo metadata failed: {}", - manifest.display(), - error - ) - })?; + .map_err(|error| format!("{}: cargo metadata failed: {}", manifest.display(), error))?; if metadata.workspace_members.is_empty() { failures.push(format!( "{}: cargo metadata produced no workspace members", @@ -505,7 +529,7 @@ fn validate_dependency_boundaries(root: &Path, failures: &mut Vec<String>) -> Re continue; } let dependencies = parse_manifest_dependencies(&text); - if !is_adapter_like_package(&package) { + if !is_adapter_like_package(&package) && !is_app_package(&package) { for dependency in &dependencies { if is_forbidden_gui_dependency(dependency) { failures.push(format!( @@ -522,13 +546,13 @@ fn validate_dependency_boundaries(root: &Path, failures: &mut Vec<String>) -> Re manifest.display() )); } - for dependency in &dependencies { - if is_forbidden_runtime_bridge_dependency(dependency) { - failures.push(format!( - "{}: app package {package} depends on forbidden bridge dependency {dependency}", - manifest.display() - )); - } + } + if package == "fparkan-headless" { + if let Some(forbidden) = first_forbidden_platform_bridge_dependency(&dependencies) { + failures.push(format!( + "{}: headless package {package} depends on platform/render bridge dependency {forbidden}", + manifest.display() + )); } } @@ -567,10 +591,7 @@ fn is_app_package(package: &str) -> bool { } fn is_adapter_like_package(package: &str) -> bool { - matches!( - package, - "fparkan-platform-winit" | "fparkan-render-vulkan" - ) + matches!(package, "fparkan-platform-winit" | "fparkan-render-vulkan") } fn first_forbidden_parser_dependency(dependencies: &BTreeSet<String>) -> Option<&str> { @@ -630,16 +651,10 @@ fn first_forbidden_platform_bridge_dependency(dependencies: &BTreeSet<String>) - }) } -fn is_forbidden_runtime_bridge_dependency(dependency: &str) -> bool { - matches!( - dependency, - "fparkan-platform-winit" | "fparkan-render-vulkan" | "winit" | "ash" | "ash-window" - ) -} - fn is_forbidden_domain_dependency(dependency: &str) -> bool { matches!( - dependency, "fparkan-cli" + dependency, + "fparkan-cli" | "fparkan-game" | "fparkan-headless" | "fparkan-viewer" @@ -889,14 +904,14 @@ fn scan_policy_file(path: &Path, failures: &mut Vec<String>) -> Result<(), Strin previous_line_has_safety_comment = false; continue; } - if contains_unsafe_construct(trimmed) { - if !is_authorized_unsafe_construct(path, trimmed, previous_line_has_safety_comment) { - failures.push(format!( - "{}:{}: unsafe construct in workspace source", - path.display(), - index + 1 - )); - } + if contains_unsafe_construct(trimmed) + && !is_authorized_unsafe_construct(path, trimmed, previous_line_has_safety_comment) + { + failures.push(format!( + "{}:{}: unsafe construct in workspace source", + path.display(), + index + 1 + )); } previous_line_has_safety_comment = false; } @@ -911,9 +926,7 @@ fn contains_unsafe_construct(line: &str) -> bool { } fn is_comment_line(line: &str) -> bool { - line.starts_with("//") - || line.starts_with("//!") - || line.starts_with("///") + line.starts_with("//") || line.starts_with("//!") || line.starts_with("///") } fn has_safety_comment(line: &str) -> bool { @@ -924,7 +937,9 @@ const AUDITED_UNSAFE_SOURCE_FILES: &[&str] = &["adapters/fparkan-render-vulkan/s fn is_audited_unsafe_source(path: &Path) -> bool { let as_path = path.as_os_str().to_string_lossy(); - AUDITED_UNSAFE_SOURCE_FILES.iter().any(|candidate| as_path.ends_with(candidate)) + AUDITED_UNSAFE_SOURCE_FILES + .iter() + .any(|candidate| as_path.ends_with(candidate)) } fn is_authorized_unsafe_construct( @@ -1258,11 +1273,7 @@ impl AcceptanceAudit { } fn strict_failures(&self) -> Vec<String> { - self.partial - .iter() - .chain(&self.missing) - .cloned() - .collect() + self.partial.iter().chain(&self.missing).cloned().collect() } } |
