# 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`, la composition/fallback WebSocket validée par `0.3.5-alpha.8.fix.1`, le POC datagram backend-spécifique validé par `0.3.5-alpha.9.fix.1`, puis la caractérisation comparative bornée validée par `0.3.5-alpha.10.fix.1`. `0.3.5-beta.1` est maintenant la tranche active de validation large, sans nouvelle fonctionnalité. 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. ## Mission `0.3.5` doit déterminer par des preuves reproductibles si WebTransport/QUIC mérite de devenir un second backend realtime aux côtés de WebSocket. La version doit : - conserver `game-realtime-transport-lib` comme frontière transport-neutral tant qu'aucun besoin commun ne justifie son évolution ; - implémenter un chemin WebTransport fiable compatible avec `TransportMessage` ; - prouver client/server natifs en loopback ; - prouver un chemin navigateur/WASM réel si la stack retenue reste viable ; - prouver le traitement des certificats de développement sans désactivation permanente de TLS ; - exercer le fallback WebSocket au niveau composition ; - comparer WebSocket et WebTransport avec des mesures locales bornées ; - challenger séparément les datagrams sans les forcer dans le contrat fiable ; - conclure `retained`, `deferred` ou `rejected` pour la trajectoire produit. La frontière reste : ```text transport ↓ wire codec ↓ session protocol ↓ synchronization ↓ authoritative simulation ``` Aucune couche supérieure n'est introduite dans `0.3.5`. ## Décisions acquises en alpha.1 ### Stack POC primaire Famille retenue : ```text web-transport 0.12.x native -> web-transport-quinn 0.12.x wasm32 -> web-transport-wasm 0.6.x ``` La version exacte résolue par Cargo sera enregistrée dans le delta qui introduit les dépendances. Les contraintes restent centralisées sous `[workspace.dependencies]` conformément à `RUST-DEP-001` et les features sont choisies localement par la crate consommatrice. `wtransport 0.7.x` reste le candidat de repli prioritaire si une exigence concrète bloque la famille primaire. Quinn/H3 brut n'est pas le premier choix. ### Fermeture de dépendances en alpha.2 La première tranche native utilise directement : ```text web-transport-quinn 0.12.1, default-features = false, feature ring rcgen 0.14.10, default-features = false, feature ring url 2.5.8 ``` La façade `web-transport` n'est pas ajoutée avant le chemin WASM : `alpha.2` ne possède qu'un backend natif et n'a pas besoin d'une abstraction multiplateforme encore inutilisée. `ring` est choisi explicitement et seul pour éviter le backend crypto par défaut `aws-lc-rs` de `web-transport-quinn` et garder le graphe POC plus petit et déterministe. L'identité locale générée est ECDSA P-256/SHA-256, contient les SAN `localhost`, `127.0.0.1` et `::1`, vit sept jours avec une petite marge de clock skew et reste uniquement en mémoire. Une identité X.509 DER + PKCS#8 DER peut aussi être injectée. Le client accepte uniquement le hash SHA-256 explicitement configuré ; aucune API de désactivation de validation TLS n'est exposée. `alpha.2` retourne une session WebTransport établie mais n'ouvre encore aucun stream applicatif et n'implémente pas `RealtimeConnection`. Cette séparation ferme la preuve QUIC/TLS avant le framing de `alpha.3`. La gate de `alpha.2` a confirmé compilation, Clippy, configuration TLS/pinning et rejet d’un mauvais pin, mais le test positif comparait strictement `127.0.0.1:port` à sa représentation IPv4-mapped IPv6 `[::ffff:127.0.0.1]:port` remontée par Quinn. `alpha.2.fix.1` normalise uniquement cette représentation dans le test d’intégration ; aucun contrat, comportement transport ou scope de `alpha.3` n’est modifié. ### Fermeture du chemin fiable en alpha.3 `alpha.3` conserve `game-realtime-transport-lib` inchangé et adapte le backend concret à ses traits. Une `WebTransportSession` devient une `WebTransportConnection` après sélection d'un unique stream bidirectionnel primaire : le client l'ouvre, le serveur l'accepte. Le `split()` conserve une copie de la session dans chaque moitié afin que la session QUIC/WebTransport ne soit pas fermée au moment où l'objet connexion est consommé. Le framing privé est exactement : ```text u32 big-endian payload length payload bytes ``` La borne POC est fixée à 1 MiB par message dans cette tranche. Elle est vérifiée avant écriture et, surtout, avant allocation côté réception. Cette limite reste interne : `alpha.4` décide sa configuration produit avec deadlines, backpressure, reset/abort/cancellation et mapping d'erreurs détaillé. Le FIN du stream primaire constitue la fermeture logique de base de `alpha.3` et produit `TransportReceive::Closed` après consommation des messages déjà écrits. La fermeture de session complète et les scénarios de lifecycle avancés restent explicitement réservés à `alpha.4`. `web-transport-quinn` écrit l'en-tête WebTransport du stream durant `open_bi()` avant de rendre le stream au code appelant. L'accept serveur peut donc terminer avant la première frame applicative ; le framing games.sasedev commence directement au premier octet applicatif et n'ajoute aucun préambule de visibilité. La gate utilisateur de `alpha.3` a ensuite confirmé fmt, audits, check workspace, Clippy strict, les sept tests du contrat commun, les sept tests unitaires/établissement WebTransport déjà présents et le round-trip primaire dédié. Le graphe inverse confirme aussi que `game-realtime-webtransport-lib` reste un backend feuille et ne remonte pas dans engine/gameplay. ### Fermeture robustesse et lifecycle en alpha.4 `alpha.4` remplace la borne interne figée par `WebTransportConfig`, partagé par les configs client/server. Les valeurs par défaut restent volontairement conservatrices pour le POC : 1 MiB par message, 10 s pour la connexion/fin de réponse CONNECT, 5 s pour l'ouverture ou l'accept du stream primaire et 5 s pour un envoi complet. Les valeurs nulles ou une taille incompatible avec le champ de longueur `u32` sont rejetées avant démarrage. La notion de deadline est volontairement opérationnelle et non un timeout d'inactivité global : - le client borne l'établissement de session ; - côté serveur, l'attente du prochain pair reste non bornée comme un listener normal, puis la réponse WebTransport après matérialisation de la requête CONNECT est bornée ; - ouverture/accept du stream primaire sont bornés ; - un envoi header + payload est borné, de sorte qu'un blocage durable sous flow-control/backpressure devient un `TransportErrorKind::Timeout` visible ; - `receive()` n'a pas de timeout d'idle implicite : l'absence de message n'est pas une panne de transport et l'appelant peut annuler sa future sans perdre l'état du framing. Le receiver utilise donc un parseur incrémental persistant fondé sur la lecture cancel-safe du backend. Une future `receive()` abandonnée après une partie du header ou du payload peut être relancée et reprend au bon octet. EOF au milieu d'une frame devient `Protocol`, tandis qu'un EOF à frontière de frame reste `TransportReceive::Closed`. Une longueur supérieure à la borne est refusée avant allocation puis la direction de réception est stoppée. Le lifecycle distingue désormais explicitement FIN propre et abandon : - `RealtimeSender::close()` émet le FIN propre ; - `WebTransportSender::abort(code)` reset la direction d'envoi ; - `WebTransportReceiver::abort(code)` stoppe la direction de réception ; - drop d'un sender/receiver encore actif provoque reset/stop au lieu de synthétiser une fermeture propre ; - cancellation d'un `send()` en cours arme un guard terminal qui reset le stream, car une frame partiellement écrite ne peut pas être reprise sans ambiguïté ; - cancellation d'un `receive()` reste non terminale grâce au parseur incrémental. Les erreurs amont restent confinées au backend : reset/STOP observables -> `Aborted`, FIN/stream/session fermé proprement -> `Closed`, framing/état de stream invalide -> `Protocol`, dépassement -> `MessageTooLarge`, deadline -> `Timeout`, et autres erreurs de session -> `Io`. Aucun type Quinn/WebTransport n'entre dans `game-realtime-transport-lib`. Aucune file applicative n'est ajoutée pour fabriquer artificiellement `Backpressure` : la pression est celle du flow-control QUIC. Lorsqu'elle empêche un envoi de terminer dans sa deadline, l'erreur observable est `Timeout`. Un test synthétique de saturation n'est pas imposé tant qu'il ne peut pas être rendu déterministe sans dépendre d'internals amont. ### Ownership physique Nouveau backend durable candidat : ```text crates/common/game-realtime-webtransport-lib ``` Responsabilités : - établissement WebTransport natif et, si viable, client WASM ; - adaptation du chemin fiable vers `game-realtime-transport-lib` ; - framing binaire privé du stream principal ; - TLS/certificat/hash nécessaires au transport ; - limites, timeouts, close/reset/cancellation et mapping d'erreurs ; - tracing `games::realtime::webtransport` ; - tests natifs déterministes ; - aucune notion joueur/room/tick/snapshot. Launchers techniques candidats : ```text crates/apps/game-realtime-webtransport-smoke crates/apps/game-realtime-transport-benchmark ``` Un launcher séparé de fallback n'est créé que si le smoke WebTransport ou le benchmark ne peut pas porter proprement cette preuve. Ne pas créer plusieurs exécutables uniquement pour refléter chaque alpha. Pour le navigateur, le frontend technique exact est décidé au moment de `alpha.7` après validation du chemin WASM. S'il faut un host Vite direct, il doit rester explicitement technique et ne pas contaminer `Web/game-snake-poc` ou le gameplay Snake. ### Contrat commun `game-realtime-transport-lib` reste inchangé dans le plan initial. Le chemin fiable utilise : ```text one WebTransport session -> one primary bidirectional reliable stream -> u32 big-endian length -> payload bytes ``` Le framing est privé au backend, borné avant allocation et distinct du futur wire codec. Le client ouvre le stream principal ; le serveur l'accepte avant de retourner une connexion utilisable. Le split commun mappe ensuite write/read du stream principal vers `RealtimeSender`/`RealtimeReceiver`. Toute modification du contrat commun requiert un besoin impossible à satisfaire proprement par WebSocket et WebTransport autrement ; elle n'est pas autorisée pour harmoniser les noms d'API. ### Datagrams Les datagrams restent hors `RealtimeConnection` parce qu'ils sont non fiables et non ordonnés. Le POC les exerce uniquement comme capacité backend-spécifique/mesure. Aucune API commune durable n'est créée sans second consommateur réel. ### TLS de développement Le POC privilégie : - certificat self-signed X.509v3 court ; - ECDSA P-256 ; - validité totale inférieure à deux semaines ; - pin SHA-256 côté client natif et navigateur ; - aucune clé privée durable versionnée ; - aucune désactivation globale de validation TLS. Le mode PKI publique, ACME et reverse proxy HTTP/3 restent hors scope de `0.3.5`. ### Fallback Le fallback vit au niveau composition/application : ```text WebTransport attempt -> success: WebTransport -> classified unavailable/establishment failure: WebSocket ``` Le test doit pouvoir forcer les deux branches. La classification est volontairement conservative : tant que le backend WebTransport natif agrège sous `TransportErrorKind::Connect` des causes d'établissement qui peuvent inclure un échec TLS/pinning, `Connect` ne doit pas déclencher un downgrade silencieux. `alpha.8` autorise uniquement `Timeout` et `Io` comme causes de fallback ; `InvalidConfiguration`, `Connect`, `Protocol`, `Aborted`, `Closed`, `Bind`, `Accept`, `MessageTooLarge` et `Backpressure` restent visibles. Pas de `TransportManager`, registry de plugins ou sélection dynamique générique dans `game-realtime-transport-lib`. ## Plateformes et preuves ### Linux natif — obligatoire - compilation backend ; - client/server loopback sur adresse/port éphémère ; - round-trip binaire ; - limites/timeouts/close/reset ; - smoke runtime hors `#[test]` ; - mesures locales WebSocket vs WebTransport. ### WASM/navigateur — obligatoire si la stack primaire reste viable Deux gates distinctes : 1. build/check réel `wasm32-unknown-unknown` du chemin WebTransport Rust ; 2. smoke runtime dans un navigateur récent vers le serveur Rust local. Le cfg `web_sys_unstable_apis` doit être ciblé sur WASM uniquement. ### Android — trajectoire, pas intégration produit - vérifier la compatibilité des dépendances et, si raisonnable, compiler le backend pour au moins `aarch64-linux-android` ; - ne modifier ni Java, ni Gradle, ni JNI sans besoin concret découvert ; - ne pas rejouer APK/AAB quatre ABI si aucun chemin Android produit n'est touché. ### Apple — documentation uniquement macOS/iOS restent non validés sans environnement Apple. La version documente la dépendance supposée mais n'attribue aucun smoke. ## Métriques Mesures obligatoires si les deux transports fonctionnent : ```text establishment latency RTT: small payload throughput: bounded medium payload several messages in flight ``` Jeu d'essai candidat : ```text 32 B 256 B 1 KiB 16 KiB ``` La taille exacte peut évoluer selon les limites observées, mais reste identique entre transports. Les résultats doivent inclure au minimum nombre d'itérations et une statistique robuste simple, par exemple médiane et p95. Aucun benchmark loopback ne conclut à lui seul sur Internet/mobile. CPU/mémoire sont optionnels si la mesure n'est pas stable/reproductible dans la session. ## Smoke tests prévus ### Smoke WebSocket hérité ```bash cargo run -p game-realtime-websocket-smoke ``` Reste le témoin de fallback et doit être rejoué aux jalons larges où le fallback est concerné. ### Smoke WebTransport natif Cible prévue : ```bash cargo run -p game-realtime-webtransport-smoke ``` Il doit : - générer/charger uniquement l'identité de développement nécessaire ; - binder localement sans port fixe ; - établir un client WebTransport ; - échanger au moins un payload dans chaque sens ; - fermer proprement ; - afficher un résultat `PASS` déterministe ; - ne dépendre ni d'Internet ni d'un secret versionné. ### Smoke navigateur Le smoke navigateur doit prouver réellement : - chargement du client WASM retenu ; - création WebTransport vers le serveur Rust ; - hash certificat transmis explicitement ; - round-trip binaire ; - close propre ; - absence de fallback silencieux vers WebSocket pendant la preuve WebTransport. Le workflow exact est fixé dans `alpha.7` après la preuve de compilation WASM. ### Smoke fallback La preuve de fallback doit couvrir : - WebTransport disponible -> chemin WebTransport ; - WebTransport volontairement indisponible/endpoint invalide -> WebSocket ; - erreur non classée comme fallback -> erreur visible, pas masquée. Cette preuve peut être intégrée à un launcher technique existant si cela garde le code plus petit et plus clair. ## Tests automatisés prévus ### Backend natif Au minimum : - configuration valide/invalide ; - établissement loopback ; - payload vide si le contrat l'autorise ; - payload binaire normal ; - plusieurs messages ordonnés ; - taille maximale et dépassement ; - longueur de frame malformée/overflow impossible ; - timeout d'établissement ; - timeout send/receive lorsque reproductible ; - fermeture locale ; - fermeture distante ; - reset/abort du stream principal ; - drop/cancellation sans task détachée ; - mapping des erreurs sans fuite des types amont dans le contrat commun. ### WASM Les tests unitaires qui n'exigent pas de navigateur restent ciblés. L'interop navigateur est un smoke runtime distinct et ne doit pas être simulée par un test natif. ### Fallback Tester la classification et la décision de composition sans créer une abstraction runtime générique. ## Graphe de dépendances attendu ```text game-realtime-transport-lib ↑ ├── game-realtime-websocket-lib └── game-realtime-webtransport-lib ``` Aucune crate sous : ```text crates/engines/ crates/games/ ``` doit dépendre de `web-transport`, Quinn, Rustls ou d'un backend concret. Les launchers techniques peuvent dépendre des backends nécessaires à leur preuve. ## Hors scope - wire codec définitif ; - session joueur/room ; - matchmaking/auth ; - snapshots/deltas gameplay ; - prediction/reconciliation/rollback ; - simulation authoritative ; - Uroburas Mode 3 ; - persistence gameplay ; - cluster/sharding/regions ; - Redis/NATS/Kafka ; - PKI/ACME produit ; - reverse proxy HTTP/3 définitif ; - CDN/edge ; - framework générique multi-transport ; - réécriture Actix Web ; - modification du gameplay pour sélectionner un transport. ## Risques et critères de replanification La version est replanifiée avant exécution lorsqu'une tranche dépasse clairement 30 minutes de scope attendu. Déclencheurs explicites : - compilation de la stack primaire impossible avec les règles Rust du dépôt ; - besoin d'un fork amont ; - browser smoke exigeant une infrastructure externe ou PKI disproportionnée ; - dépendance crypto rendant Android ou Linux non praticable ; - évolution du contrat commun requise ; - framing fiable beaucoup plus complexe que prévu ; - fallback nécessitant une abstraction partagée nouvelle ; - benchmark devenant un sous-projet de performance. Dans ces cas, le delta courant reste fermé sur sa preuve et le plan est révisé avant la tranche suivante. ## Forecast révisé Chaque tranche vise environ 15 à 30 minutes de travail effectif et une preuve indépendante. Les numéros restent prévisionnels conformément à `SESSION-007`. ### `0.3.5-alpha.1` — cadrage, audit et plan - audit archive/règles/historique `0.3.4` ; - recherche stacks WebTransport/QUIC ; - choix primaire + fallback technique ; - décision contrat/framing/datagram ; - matrice TLS/plateforme ; - smoke/tests/metrics ; - plan vivant. Aucun backend ni dépendance WebTransport ajouté. ### `0.3.5-alpha.2` — crate backend, dépendances et établissement natif - créer `game-realtime-webtransport-lib` ; - ajouter uniquement les dépendances/features nécessaires ; - config native client/server ; - génération ou injection d'identité de test ; - hash pinning ; - établir une session native client/server ; - tests d'établissement/configuration ; - README initial de responsabilité/frontières. Pas encore d'implémentation complète `RealtimeConnection` si cela rend la tranche trop lourde. ### `0.3.5-alpha.3` — stream fiable et contrat commun Tranche validée avec : - stream bidirectionnel principal ; - framing `u32 + payload` borné avant allocation ; - borne POC interne de 1 MiB ; - `RealtimeConnection`, sender et receiver ; - round-trip ordonné multi-message, y compris payload vide et octets non UTF-8 ; - fermeture distante de base par FIN du stream primaire ; - tests loopback ciblés ; - `USAGE.md` pour la séquence session -> stream primaire -> contrat commun. La gate utilisateur du 2026-09-21 est conservée dans `history/0.3.5/alpha.3.md`. ### `0.3.5-alpha.4` — robustesse et lifecycle Tranche candidate matérialisée avec : - `WebTransportConfig` et borne de message configurable, validée avant démarrage ; - deadlines de connexion/réponse CONNECT, stream primaire et send ; - flow-control QUIC conservé comme backpressure naturelle, avec timeout observable si un send reste bloqué ; - FIN propre distinct des reset/stop explicites ; - drop actif -> abort au lieu de FIN synthétique ; - cancellation d'un send partiel -> reset terminal ; - receive incrémental cancel-safe et reprenable ; - EOF au milieu d'une frame et framing invalide -> `Protocol` ; - dépassement entrant refusé avant allocation ; - mapping backend -> `Timeout`, `MessageTooLarge`, `Closed`, `Protocol`, `Aborted` ou `Io` ; - tests ciblés sur limites, reset/drop, deadline du stream, cancellation de receive et frame tronquée. Un timeout d'idle de `receive()` n'est volontairement pas imposé au contrat : l'appelant peut borner/annuler l'attente sans corrompre l'état du frame parser. Un test artificiel de saturation/backpressure n'est pas ajouté tant qu'il n'est pas déterministe. La gate utilisateur de `alpha.4` a confirmé fmt, audits, `cargo check`, tous les tests du contrat commun et les vingt tests WebTransport, mais Clippy strict a détecté un unique `clippy::implicit-return` dans la future retournée par `RealtimeReceiver::receive()`. `alpha.4.fix.1` a explicité ce `return` sans changement sémantique, d'API, de framing, de lifecycle ou de dépendance. Sa gate complète est ensuite passée : audits, check workspace, Clippy strict, sept tests du contrat commun et vingt tests WebTransport. La preuve est conservée dans `history/0.3.5/alpha.4.fix.1.md`. ### `0.3.5-alpha.5` — smoke natif hors harness Tranche validée avec : - nouvelle app technique `game-realtime-webtransport-smoke` ; - bind UDP loopback sur port éphémère ; - identité TLS ECDSA P-256 locale générée en mémoire et pin SHA-256 dérivé de cette identité ; - établissement WebTransport client/server réel sur `https://127.0.0.1:/smoke` ; - ouverture/accept du stream fiable principal via l'API publique du backend ; - round-trip binaire bidirectionnel via `RealtimeConnection` ; - FIN propre client puis serveur, chacun observé comme `TransportReceive::Closed` par le pair ; - timeout global du launcher et verdict terminal déterministe `game-realtime-webtransport-smoke: PASS` ; - aucune nouvelle dépendance externe et aucun couplage engine/gameplay. Cette tranche reste distincte pour ne pas mélanger robustesse de bibliothèque et preuve runtime publique. La gate utilisateur du 2026-09-21 a confirmé fmt, audits, check workspace, Clippy strict, les sept tests du contrat commun, les vingt tests WebTransport, le smoke hors harness avec verdict `PASS` et les graphes direct/inverse attendus. La preuve est conservée dans `history/0.3.5/alpha.5.md`. ### `0.3.5-alpha.6` — chemin WASM compilable Tranche candidate matérialisée avec : - `web-transport-wasm 0.6.0` comme dépendance exclusivement `wasm32` ; - `web_sys_unstable_apis` ciblé par Cargo uniquement sur `wasm32-unknown-unknown`, y compris rustdoc ; - dépendances Quinn/Tokio/rcgen du backend déplacées dans la branche native afin que le build WASM ne tire pas la stack serveur ; - même surface client publique qu'en natif pour hash SHA-256, config endpoint, `connect`, session et stream primaire ; - pin navigateur transmis via `serverCertificateHashes` ; - même framing fiable `u32` big-endian + payload et même limite avant allocation/écriture ; - `RealtimeConnection`, sender et receiver adaptés à des types navigateur `!Send`, ce que le contrat commun autorise déjà ; - FIN, reset/STOP, cancellation de send terminale et réception incrémentale conservés sur le chemin navigateur ; - aucun changement Snake/Reflex et aucun host frontend. La gate utilisateur du 2026-09-22 a confirmé le host natif inchangé, les sept tests du contrat commun, les vingt tests WebTransport, le smoke natif `PASS`, puis `cargo check`, Clippy `--lib` et build réel de `game-realtime-webtransport-lib` sur `wasm32-unknown-unknown`. Le graphe WASM ne tire ni Quinn, ni rcgen, ni Tokio. La preuve est conservée dans `history/0.3.5/alpha.6.md`. Différence fermée dans `alpha.7` : les deadlines `WebTransportConfig` sont matérialisées côté navigateur avec un timer WASM. `connect_timeout`, `primary_stream_timeout` et `send_timeout` sont bornés ; un send expiré reset le stream comme en natif. `receive()` reste volontairement sans timeout d'idle implicite. Les durées navigateur doivent tenir dans `u32` millisecondes, plage du timer retenu, sinon la configuration est refusée avant établissement. ### `0.3.5-alpha.7` — interop navigateur et TLS local Tranche candidate matérialisée avec : - `gloo-timers` target-specific WASM pour appliquer les deadlines sans runtime Tokio navigateur ; - `game-realtime-webtransport-browser-smoke`, package technique avec pair serveur natif et adapter `cdylib` WASM ; - host direct `Web/game-realtime-webtransport-browser-smoke` en Vite/TypeScript sur `127.0.0.1:1435` ; - page loopback vérifiant `window.isSecureContext` et la présence de WebTransport avant d'appeler le Rust/WASM ; - serveur Rust sur port QUIC éphémère, certificat P-256 court en mémoire et SHA-256 imprimé explicitement ; - endpoint/hash passés au frontend par query string ou saisie manuelle ; - navigateur -> serveur Rust WebTransport sans fallback WebSocket ; - round-trip binaire dans les deux sens via le stream primaire et `RealtimeConnection` ; - FIN navigateur observé côté serveur, puis FIN serveur observé côté navigateur ; - verdict `game-realtime-webtransport-browser-smoke: PASS` des deux côtés ; - aucune désactivation de TLS et aucun secret/certificat versionné. La page Vite peut rester servie en HTTP sur loopback : `127.0.0.1` est un origin potentiellement fiable/secure context dans les navigateurs conformes. Le transport lui-même reste impérativement `https://` et utilise `serverCertificateHashes` avec le certificat X.509v3 court ECDSA P-256 déjà produit par le backend. Cette tranche reste volontairement séparée de `alpha.8` : le smoke WebTransport navigateur ne doit contenir aucun fallback silencieux. La sélection/fallback WebSocket est prouvée ensuite au niveau composition. Correctif `0.3.5-alpha.7.fix.1` : la première gate utilisateur a validé fmt, audits, check/Clippy natifs, tests, smoke natif, check/Clippy/build WASM et la compilation de l'adapter navigateur, puis a échoué uniquement dans `npm run build` lorsque `wasm-bindgen` cherchait l'artefact Cargo sous `../../builds/...`. Depuis `Web/game-realtime-webtransport-browser-smoke`, le target-dir workspace `../builds/sasedev-games/target` se trouve en réalité sous `../../../builds/...`. Le fix corrige les scripts `wasm:dev`/`wasm:build` et l'audit de distribution associé, sans changement Rust/runtime. Correctif `0.3.5-alpha.7.fix.2` : la gate du premier fix valide fmt, audits, check/Clippy natifs et le build WASM de l'adapter, puis `npm run build` atteint TypeScript et échoue uniquement parce que l'alias `@webtransport-browser-smoke-wasm` cherche encore les déclarations wasm-bindgen sous `../../builds/...`. Le chemin `paths` du `tsconfig.json` est aligné sur `../../../builds/...`; l'audit de distribution ancre désormais aussi cette résolution. Les erreurs `implicit any` observées sur les callbacks sont une conséquence de ce module non résolu et disparaissent lorsque la déclaration générée est chargée. Aucun code Rust/runtime ni protocole n'est modifié. La gate utilisateur de `alpha.7.fix.2` valide ensuite fmt, audits, check/Clippy workspace, build WASM de l'adapter, `npm run build`, génération `wasm-bindgen`, TypeScript/Vite et le smoke navigateur réel. Une première tentative interactive a atteint la deadline browser de 5 s sans défaut reproductible établi ; une tentative suivante contre le même peer local a été acceptée et les deux côtés ont terminé par `game-realtime-webtransport-browser-smoke: PASS`. La preuve est conservée dans `history/0.3.5/alpha.7.fix.2.md`. ### `0.3.5-alpha.8` — fallback WebSocket au niveau composition Tranche candidate matérialisée avec : - nouveau launcher technique `game-realtime-transport-fallback-smoke`, sans modification du contrat commun ; - sélecteur privé WebTransport-first, non réutilisé comme framework produit ; - branche réelle WebTransport disponible avec round-trip et fermeture propres ; - branche fallback forcée par un `Timeout` WebTransport classifié, puis vraie connexion WebSocket loopback avec round-trip et fermeture propres ; - branche non-fallback utilisant une vraie configuration WebTransport invalide (`connect_timeout = 0`) et vérifiant que l'erreur `InvalidConfiguration` reste visible ; - politique volontairement étroite : seuls `Timeout` et `Io` autorisent le fallback ; - `Connect` explicitement exclu tant que le backend WebTransport ne distingue pas suffisamment les échecs réseau des échecs TLS/pinning ; - tests unitaires du sélecteur et de la matrice des `TransportErrorKind` ; - aucun couplage engine/gameplay, aucune registry et aucun `TransportManager`. 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é Le backend fiable natif et le navigateur étant désormais validés, la tranche est conservée avec un scope strict : - `WebTransportSession` expose `max_datagram_size()`, `send_datagram(...)` et `receive_datagram()` comme capacité backend-spécifique ; - le natif utilise directement les datagrams de `web-transport-quinn` ; - le chemin WASM expose la même capacité via `web-transport-wasm` et reste couvert par une gate de compilation ; - un smoke natif séparé effectue un échange datagram bidirectionnel sur loopback sous deadlines locales ; - un test d'intégration couvre le round-trip et le rejet local d'un payload supérieur à la taille courante de session ; - aucune garantie de livraison ou d'ordre n'est introduite ni testée comme invariant ; - aucune modification n'est apportée à `game-realtime-transport-lib` ou `RealtimeConnection`. Le smoke navigateur interactif n'est pas rouvert dans cette tranche : sa preuve fiable `alpha.7.fix.2` reste intacte. La valeur retenue est **future capability backend-spécifique utile**, à mesurer en `alpha.10`, sans promotion vers le contrat commun. Correctif `0.3.5-alpha.9.fix.1` : la gate utilisateur de `alpha.9` confirme fmt/audits, `cargo check --workspace`, les tests WebTransport dont `2/2` tests datagram, le smoke datagram natif terminé par `PASS`, ainsi que check/Clippy/build WASM. Clippy workspace échoue uniquement parce que le nouveau crate d’intégration `tests/datagrams.rs` ne possède pas de rustdoc de niveau crate sous `missing_docs = "warn"` promu en erreur par `-D warnings`. Le correctif ajoute cette documentation sans modifier API, comportement, test ou dépendance. ### `0.3.5-alpha.10` — mesures comparatives bornées La tranche ajoute `game-realtime-transport-measure`, un outil localhost déterministe qui ne modifie aucun backend et ne publie aucune API commune nouvelle. La méthode est figée ainsi : - 8 établissements complets par backend ; WebTransport inclut la session QUIC/HTTP3 et l'ouverture du stream primaire, WebSocket inclut le handshake ; - RTT fiable : 16 warmups puis 128 ping/pong de 32 octets ; - throughput fiable : 128 messages de 64 KiB, soit 8 MiB utiles, avec ACK final après drainage côté serveur ; - fenêtre applicative sans ACK intermédiaire : 64 messages de 1 KiB ; - datagram WebTransport séparé : 64 envois de taille `min(256, max_datagram_size client, max_datagram_size serveur)`, nombre reçu observé sous deadline locale ; - sortie textuelle stable `MEASURE ...` suivie de `game-realtime-transport-measure: PASS`. Les métriques fiables utilisent strictement `RealtimeConnection`; le chemin datagram reste sur `WebTransportSession`. Les chiffres sont une caractérisation loopback de la machine qui exécute la gate, pas un benchmark Internet/mobile ni une preuve de supériorité d'un protocole. Aucun seuil arbitraire de victoire n'est introduit. La conclusion technique provisoire est **`retain`** : conserver WebTransport comme second backend à côté de WebSocket pour la suite du projet, compte tenu du chemin natif+navigateur validé, du fallback classifié et de la capacité datagram optionnelle. Cette conclusion ne signifie pas « WebTransport est plus rapide » ; les mesures de la gate utilisateur seront enregistrées dans l'historique après exécution. La méthode, le format des résultats et leurs limites sont détaillés dans `docs/studies/027-V0_3_5_REALTIME_TRANSPORT_MEASUREMENT.md`. Correctif `0.3.5-alpha.10.fix.1` : la gate utilisateur de `alpha.10` confirme les audits, `cargo check --workspace`, les suites ciblées WebSocket/WebTransport et l’exécution release complète de `game-realtime-transport-measure`, terminée par `PASS` avec toutes les lignes `MEASURE`. La gate complète échoue uniquement sur Clippy workspace pour un import `RealtimeConnection` devenu inutilisé dans le launcher de mesure. Le correctif retire cet import sans modifier le protocole de mesure, les métriques produites, les backends ou la conclusion technique provisoire. ### `0.3.5-beta.1` — validation large Jalon rare de full workspace, désormais actif après validation de `alpha.10.fix.1`. Cette tranche ne porte aucun comportement nouveau. Gate principale : ```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 --workspace --all-targets --all-features ``` Preuves realtime et multiplateformes retenues : - smoke WebSocket natif ; - smoke WebTransport fiable natif ; - smoke datagram WebTransport natif ; - preuve WebTransport-first puis fallback WebSocket classifié ; - caractérisation release bornée ; - check/Clippy/build `wasm32-unknown-unknown` de `game-realtime-webtransport-lib` ; - build Vite/TypeScript/WASM du host browser et smoke navigateur réel ; - check Android `aarch64-linux-android` du backend WebTransport si la cible/NDK de l'environnement sont disponibles, sans APK/AAB ni modification Gradle/JNI ; - `cargo tree` direct/inverse des deux backends et vérification qu'aucun engine/gameplay ne dépend d'un backend concret. Toute capacité fonctionnelle majeure manquante réouvre une alpha ; un défaut fermé de validation/build produit `beta.1.fix.N`. ### `0.3.5-beta.2` — consolidation pré-RC Tranche explicitement réservée par `SESSION-009` / `VER-PHASE-010` : - réconcilier README/USAGE et docs durables ; - figer la conclusion WebTransport ; - documenter plateformes réellement validées/non validées ; - mettre à jour `CHANGELOG.md` si la transition vers RC est préparée ; - réconcilier `ROADMAP.md` seulement si le statut macro change ; - écrire le prompt `0.3.6` ; - enregistrer l'historique `beta.1` après validation ; - aucune nouvelle fonctionnalité majeure. ### `0.3.5-rc.1` — candidate gelée - aucun comportement volontaire nouveau ; - full workspace conformément à `CMD-RC-002` ; - gates realtime de publication ; - smokes retenus ; - graphe de dépendances ; - vérification du prompt `0.3.6` ; - corrections uniquement selon `VER-RC-*`. ### `0.3.5` — stable Promotion mécanique autant que possible : - version stable ; - historique RC ; - changelog stable ; - clôture du plan ; - roadmap si nécessaire ; - delta final ; - ajustement mécanique du prompt `0.3.6`. ## Gates Cargo planifiées ### Tranches Rust ordinaires Dès qu'un fichier Rust/Cargo change : ```bash cargo fmt --all cargo fmt --all -- --check python3 scripts/audit_rust_workspace_rules.py cargo check --workspace cargo clippy --workspace --all-targets --all-features -- -D warnings ``` Puis tests ciblés des crates touchées/consommateurs affectés. ### Full workspace `cargo test --workspace --all-targets --all-features` est réservé explicitement à : - `beta.1` ; - `rc.1` ; - éventuellement une tranche plus tôt uniquement si un changement transverse rend les tests ciblés insuffisants. Il n'est pas répété à chaque alpha. ### Cargo tree À exécuter : - après introduction/changement de dépendances WebTransport ; - à `alpha.5` pour prouver la frontière ; - à `beta.1` et `rc.1` pour les graphes de publication. ## Nettoyage Cargo Aucun `cargo clean` n'est requis dans `alpha.1`. Le plan réserve un nettoyage complet au plus tard avant la validation RC si l'accumulation du target-dir ou un doute de reproductibilité le justifie. Un nettoyage ciblé reste préférable pendant les alphas. ## Critère de réussite de 0.3.5 La version est réussie si elle fournit une conclusion reproductible parmi : ```text retained -> second backend utile, conserver pour 0.4.x deferred -> viable mais bénéfice/portabilité/infrastructure insuffisants aujourd'hui rejected -> coût ou incompatibilité disproportionnés pour la trajectoire actuelle ``` Aucune conclusion n'est imposée à l'avance. Même en cas de report/rejet, la baseline WebSocket `0.3.4` reste fonctionnelle et le POC ne doit pas laisser une abstraction commune artificiellement déformée.