diff --git a/Cargo.toml b/Cargo.toml index 78c310c..d4ab77a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ members = [ ] [workspace.package] -version = "0.3.5-alpha.8" +version = "0.3.5-alpha.8.fix.1" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/games" diff --git a/README.md b/README.md index 5e4df04..4aba6fb 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale Version stable de référence : `0.3.4`. -Version active : `0.3.5-alpha.8`. `0.3.2` reste différée. +Version active : `0.3.5-alpha.8.fix.1`. `0.3.2` reste différée. -La stable `0.3.4` livre la première baseline realtime : contrat binaire transport-neutral, backend WebSocket Tokio/tokio-tungstenite, limites et deadlines, tests loopback/robustesse, smoke runtime localhost public et frontières de dépendances empêchant moteurs et gameplay de dépendre d'un backend concret. `0.3.5-alpha.8` conserve les chemins WebTransport natif et navigateur validés, puis ajoute une preuve de composition WebTransport-first avec fallback WebSocket strictement classifié. Le fallback n'est autorisé que pour `Timeout`/`Io`; les erreurs ambiguës ou sensibles, notamment `Connect`, `InvalidConfiguration` et `Protocol`, restent visibles et ne déclenchent aucun downgrade silencieux. Les datagrams et les mesures comparatives restent réservés aux tranches suivantes. +La stable `0.3.4` livre la première baseline realtime : contrat binaire transport-neutral, backend WebSocket Tokio/tokio-tungstenite, limites et deadlines, tests loopback/robustesse, smoke runtime localhost public et frontières de dépendances empêchant moteurs et gameplay de dépendre d'un backend concret. `0.3.5-alpha.8.fix.1` conserve les chemins WebTransport natif et navigateur validés, puis ajoute une preuve de composition WebTransport-first avec fallback WebSocket strictement classifié. Le fallback n'est autorisé que pour `Timeout`/`Io`; les erreurs ambiguës ou sensibles, notamment `Connect`, `InvalidConfiguration` et `Protocol`, restent visibles et ne déclenchent aucun downgrade silencieux. Les datagrams et les mesures comparatives restent réservés aux tranches suivantes. Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme. diff --git a/crates/apps/game-realtime-transport-fallback-smoke/src/main.rs b/crates/apps/game-realtime-transport-fallback-smoke/src/main.rs index 858e2c1..e731e9b 100644 --- a/crates/apps/game-realtime-transport-fallback-smoke/src/main.rs +++ b/crates/apps/game-realtime-transport-fallback-smoke/src/main.rs @@ -7,7 +7,6 @@ //! Executable localhost smoke for WebTransport-first realtime composition with classified WebSocket fallback. -use game_realtime_transport_lib::RealtimeConnection; // rust-rules: trait-import use game_realtime_transport_lib::RealtimeReceiver; // rust-rules: trait-import use game_realtime_transport_lib::RealtimeSender; // rust-rules: trait-import @@ -107,15 +106,19 @@ async fn run_webtransport_preferred_branch() -> std::result::Result<(), String> return session.accept_primary_connection().await; }; let client = select_preferred_transport( - || async { - let session = match game_realtime_webtransport_lib::connect(&client_config).await { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => return std::result::Result::Err(error), + || { + return async { + let session = match game_realtime_webtransport_lib::connect(&client_config).await { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return session.open_primary_connection().await; }; - return session.open_primary_connection().await; }, - || async { - return game_realtime_websocket_lib::connect(UNUSED_WEBSOCKET_ENDPOINT).await; + || { + return async { + return game_realtime_websocket_lib::connect(UNUSED_WEBSOCKET_ENDPOINT).await; + }; }, ); let (server_result, client_result) = tokio::join!(server, client); @@ -146,13 +149,17 @@ async fn run_websocket_fallback_branch() -> std::result::Result<(), String> { let endpoint = format!("ws://{}/fallback", listener.local_addr()); let server = listener.accept(); let client = select_preferred_transport( - || async { - return std::result::Result::::Err( - game_realtime_transport_lib::TransportError::new(game_realtime_transport_lib::TransportErrorKind::Timeout, FORCED_FALLBACK_DETAIL), - ); + || { + return async { + return std::result::Result::::Err( + game_realtime_transport_lib::TransportError::new(game_realtime_transport_lib::TransportErrorKind::Timeout, FORCED_FALLBACK_DETAIL), + ); + }; }, - || async { - return game_realtime_websocket_lib::connect(endpoint.as_str()).await; + || { + return async { + return game_realtime_websocket_lib::connect(endpoint.as_str()).await; + }; }, ); let (server_result, client_result) = tokio::join!(server, client); @@ -184,15 +191,19 @@ async fn run_non_fallback_error_branch() -> std::result::Result<(), String> { }, }; let result = select_preferred_transport( - || async { - let session = match game_realtime_webtransport_lib::connect(&client_config).await { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => return std::result::Result::Err(error), + || { + return async { + let session = match game_realtime_webtransport_lib::connect(&client_config).await { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return session.open_primary_connection().await; }; - return session.open_primary_connection().await; }, - || async { - return game_realtime_websocket_lib::connect("ws://127.0.0.1:1/non-fallback-must-not-run").await; + || { + return async { + return game_realtime_websocket_lib::connect("ws://127.0.0.1:1/non-fallback-must-not-run").await; + }; }, ) .await; diff --git a/crates/apps/game-realtime-transport-fallback-smoke/unit_tests/fallback.rs b/crates/apps/game-realtime-transport-fallback-smoke/unit_tests/fallback.rs index 436825c..c01873f 100644 --- a/crates/apps/game-realtime-transport-fallback-smoke/unit_tests/fallback.rs +++ b/crates/apps/game-realtime-transport-fallback-smoke/unit_tests/fallback.rs @@ -19,34 +19,46 @@ fn fallback_policy_is_narrow_and_excludes_ambiguous_connect_failures() { #[tokio::test(flavor = "current_thread")] async fn selector_prefers_webtransport_and_falls_back_only_for_classified_errors() { let preferred = super::select_preferred_transport( - || async { - return std::result::Result::::Ok(7); + || { + return async { + return std::result::Result::::Ok(7); + }; }, - || async { - return std::result::Result::::Err(test_error("fallback must not run")); + || { + return async { + return std::result::Result::::Err(test_error("fallback must not run")); + }; }, ) .await; assert!(matches!(preferred, std::result::Result::Ok(super::SelectedConnection::WebTransport(7))), "WebTransport success must remain preferred"); let fallback = super::select_preferred_transport( - || async { - return std::result::Result::::Err(game_realtime_transport_lib::TransportError::new( - game_realtime_transport_lib::TransportErrorKind::Timeout, - "forced timeout", - )); + || { + return async { + return std::result::Result::::Err(game_realtime_transport_lib::TransportError::new( + game_realtime_transport_lib::TransportErrorKind::Timeout, + "forced timeout", + )); + }; }, - || async { - return std::result::Result::::Ok(9); + || { + return async { + return std::result::Result::::Ok(9); + }; }, ) .await; assert!(matches!(fallback, std::result::Result::Ok(super::SelectedConnection::WebSocket(9))), "classified timeout must select WebSocket fallback"); let non_fallback = super::select_preferred_transport( - || async { - return std::result::Result::::Err(test_error("visible primary error")); + || { + return async { + return std::result::Result::::Err(test_error("visible primary error")); + }; }, - || async { - return std::result::Result::::Ok(11); + || { + return async { + return std::result::Result::::Ok(11); + }; }, ) .await; diff --git a/deltas/0.3.5/alpha.8.fix.1.md b/deltas/0.3.5/alpha.8.fix.1.md new file mode 100644 index 0000000..fd93fac --- /dev/null +++ b/deltas/0.3.5/alpha.8.fix.1.md @@ -0,0 +1,123 @@ + + + +# Delta 0.3.5-alpha.8.fix.1 + +## Base requise + +`0.3.5-alpha.8`. + +La gate utilisateur du 2026-09-22 valide : + +- fmt et les trois audits ; +- `cargo check --workspace` ; +- les suites `game-realtime-transport-lib`, `game-realtime-websocket-lib` et `game-realtime-webtransport-lib` ; +- les deux tests du launcher fallback ; +- le smoke WebSocket ; +- le smoke WebTransport ; +- le smoke de composition fallback, terminé par `game-realtime-transport-fallback-smoke: PASS` ; +- les graphes de dépendances attendus. + +La gate complète échoue uniquement sur Clippy strict dans `game-realtime-transport-fallback-smoke` : + +```text +unused import: game_realtime_transport_lib::RealtimeConnection +missing return statement +``` + +Les erreurs `implicit-return` portent uniquement sur des closures qui retournent une future `async`. + +`alpha.8` n'est donc pas historisée comme validée avant ce correctif. + +## Cause + +Le launcher importe explicitement `RealtimeConnection` alors que ses bounds utilisent le chemin qualifié complet : l'import n'est jamais consommé. + +En parallèle, la règle workspace `clippy::implicit-return` s'applique aussi à la valeur de retour des closures. Les formes : + +```rust +|| async { ... } +``` + +doivent donc être écrites : + +```rust +|| return async { ... } +``` + +même lorsque le corps `async` contient déjà ses propres `return` explicites. + +## Correction + +La version technique passe à : + +```text +0.3.5-alpha.8.fix.1 +``` + +Le correctif : + +- supprime l'import inutilisé `RealtimeConnection` de `src/main.rs` ; +- rend explicite le `return` de toutes les closures async du sélecteur dans `src/main.rs` ; +- applique la même forme aux closures async des tests unitaires. + +Aucun comportement runtime, backend, contrat, mapping d'erreur, endpoint, framing, TLS/pinning ou dépendance n'est modifié. + +La politique de fallback reste strictement : + +```text +Timeout -> fallback autorisé +Io -> fallback autorisé +Connect -> fallback interdit +``` + +et toutes les autres catégories restent non-fallback. + +## Fichiers modifiés + +```text +Cargo.toml +README.md +crates/apps/game-realtime-transport-fallback-smoke/src/main.rs +crates/apps/game-realtime-transport-fallback-smoke/unit_tests/fallback.rs +docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md +``` + +Nouveau fichier : + +```text +deltas/0.3.5/alpha.8.fix.1.md +``` + +Aucun fichier `history/0.3.5/alpha.8.md` n'est créé avant une gate entièrement verte. + +## Plan + +Le forecast fonctionnel ne change pas. Après validation de ce fix, `0.3.5-alpha.9` reste la tranche suivante pour le POC datagram isolé si elle demeure justifiée par l'API/backend réellement disponibles. + +## Validation attendue + +```bash +cargo fmt --all +cargo fmt --all -- --check + +python3 scripts/audit_rust_workspace_rules.py +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history +python3 scripts/audit_distribution_layout.py + +cargo check --workspace +cargo clippy --workspace --all-targets --all-features -- -D warnings + +cargo test -p game-realtime-transport-fallback-smoke --all-targets --all-features +cargo run -p game-realtime-transport-fallback-smoke +``` + +Le smoke doit terminer par : + +```text +game-realtime-transport-fallback-smoke: PASS +``` + +## Après validation + +Créer `history/0.3.5/alpha.8.fix.1.md` à partir des résultats réellement fournis, puis ouvrir directement `0.3.5-alpha.9` si le POC datagram reste pertinent. diff --git a/docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md b/docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md index 83db074..39abe74 100644 --- a/docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md +++ b/docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md @@ -1,11 +1,11 @@ - + # Plan 0.3.5 — POC WebTransport/QUIC et fallback WebSocket ## Statut -Plan actif créé pendant `0.3.5-alpha.1` à partir de l'archive taggée `v0.3.4`, puis réconcilié pour l'implémentation native de `0.3.5-alpha.2`, son correctif de validation `0.3.5-alpha.2.fix.1`, le chemin fiable validé de `0.3.5-alpha.3`, la robustesse validée par `0.3.5-alpha.4.fix.1`, le smoke natif validé de `0.3.5-alpha.5`, le chemin client WASM validé de `0.3.5-alpha.6`, le smoke navigateur validé par `0.3.5-alpha.7.fix.2` et la composition/fallback WebSocket candidate de `0.3.5-alpha.8`. +Plan actif créé pendant `0.3.5-alpha.1` à partir de l'archive taggée `v0.3.4`, puis réconcilié pour l'implémentation native de `0.3.5-alpha.2`, son correctif de validation `0.3.5-alpha.2.fix.1`, le chemin fiable validé de `0.3.5-alpha.3`, la robustesse validée par `0.3.5-alpha.4.fix.1`, le smoke natif validé de `0.3.5-alpha.5`, le chemin client WASM validé de `0.3.5-alpha.6`, le smoke navigateur validé par `0.3.5-alpha.7.fix.2` et la composition/fallback WebSocket corrigée par `0.3.5-alpha.8.fix.1`. Le cadrage détaillé et la comparaison des stacks actuelles sont conservés dans `docs/studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md`. Le présent document porte les décisions opérationnelles, le scope, les gates et le forecast vivant de la version. @@ -535,6 +535,8 @@ Tranche candidate matérialisée avec : Le smoke navigateur `alpha.7` reste inchangé et continue d'échouer explicitement si WebTransport échoue ; le fallback n'existe que dans le launcher de composition `alpha.8`. +Correctif `0.3.5-alpha.8.fix.1` : la gate utilisateur de `alpha.8` valide fmt, audits, `cargo check --workspace`, les suites transport/WebSocket/WebTransport, les trois smokes runtime et les graphes de dépendances. Le smoke fallback termine bien par `PASS`, mais Clippy strict échoue uniquement sur un import de trait devenu inutilisé et sur les closures `|| async { ... }` qui doivent respecter la règle workspace `clippy::implicit-return`. Le correctif retire l'import inutile et écrit explicitement `|| return async { ... }` dans le launcher et ses tests, sans modifier la politique de fallback ni aucun backend. + ### `0.3.5-alpha.9` — datagram POC isolé Uniquement si le backend fiable et le navigateur sont suffisamment stables :