# Plan 0.3.4 — transport realtime et baseline WebSocket ## Statut Plan actif créé pendant `0.3.4-alpha.1` à partir de l'archive taggée `v0.3.3`. Les tranches `alpha.1`, `alpha.2`, `alpha.3.fix.1` et `alpha.4` ont été validées le 2026-09-21. `alpha.3` avait été rejetée avant compilation à cause d'un héritage Cargo invalide de `default-features`; son fix a ensuite validé le backend WebSocket, le round-trip localhost et le graphe sans TLS. `alpha.4` a fermé les limites, timeouts, cas négatifs et lifecycle. La revue post-gate a toutefois identifié une preuve encore absente : un smoke runtime exécuté hors harness de test. La tranche active `alpha.5` ajoute donc ce smoke avant toute entrée en beta. ## Mission `0.3.4` doit introduire une frontière de transport realtime async et une première implémentation WebSocket fondée sur Tokio + `tokio-tungstenite`, sans implémenter le protocole de session, la synchronisation gameplay ni la simulation authoritative. La frontière reste : ```text transport ↓ wire codec ↓ session protocol ↓ synchronization ↓ authoritative simulation ``` Le transport de `0.3.4` transporte des octets opaques. Il ne connaît ni joueur, ni room, ni tick, ni snapshot, ni delta métier. ## Baseline auditée L'archive fournie comme téléchargement du tag `v0.3.3` est traitée comme autoritaire conformément à `CMD-GIT-003` et `CMD-GIT-004`. L'audit local de l'archive confirme : - archive ZIP intègre, sans chemin traversant ni symlink ; - `400` fichiers, `14` membres Cargo et `55` fichiers Rust ; - `workspace.package.version = 0.3.3` avant migration ; - audits Rust/workspace, Markdown et distribution propres sur l'archive ; - aucune arborescence générée `target/`, `node_modules/`, `gen/android` ou `build/` livrée ; - gameplay et moteurs sans dépendance réseau realtime ; - baseline Android `0.3.3` conservée et hors scope de cette version. Le log utilisateur fourni à l'ouverture de session confirme également `cargo fmt`, `cargo check --workspace` et Clippy workspace strict sur son checkout `0.3.3`. Son audit Markdown annonce davantage de fichiers que l'archive taggée fournie ; la présente version reste néanmoins construite exclusivement depuis l'archive autoritaire reçue et n'invente aucun fichier absent de celle-ci. ## Migration de gouvernance À partir de `0.3.4`, les seules formes de prerelease nouvelles sont : ```text X.Y.Z-alpha.N X.Y.Z-alpha.N.fix.M X.Y.Z-beta.N X.Y.Z-beta.N.fix.M X.Y.Z-rc.N X.Y.Z-rc.N.fix.M ``` `alpha.1` remplace l'ancien rôle de cadrage `0-pre.1`. Les anciens labels `0-pre`, `1-alpha`, `2-beta` et `3-rc` restent valides uniquement comme preuves historiques déjà livrées. Les audits distinguent donc la convention courante de la compatibilité historique et n'autorisent plus l'ancien schéma pour un nouveau workspace ou un nouvel historique `0.3.4+`. ## Dépendances vérifiées au 2026-09-21 Versions amont retenues et introduites par `alpha.3` : ```text Tokio 1.53.1 futures-util 0.3.34 tokio-tungstenite 0.30.0 tungstenite 0.30.0, via réexport tokio-tungstenite ``` Contraintes utiles : - Tokio `1.53.1` annonce un MSRV `1.71` ; - `tokio-tungstenite 0.30.0` et `tungstenite 0.30.0` annoncent un MSRV `1.85` ; - `tokio-tungstenite` dépend déjà de Tokio `1.x` et de Tungstenite `0.30.0` ; - ses features par défaut couvrent `connect` et `handshake`, sans TLS ; - `native-tls` et les variantes `rustls-*` sont optionnelles ; - Tungstenite expose déjà des limites de message/frame et de write buffer configurables. `alpha.3.fix.1` centralise désormais aussi `default-features = false` sous `[workspace.dependencies]`, car Cargo interdit à une dépendance membre héritée de remplacer cette option. `game-realtime-websocket-lib` ajoute seulement les features locales `sink + std` pour `futures-util` et `connect + handshake` pour `tokio-tungstenite`; aucune feature TLS n'est activée. La gate utilisateur du fix confirme Rust `1.94.1`, Tokio `1.53.1`, tokio-tungstenite/Tungstenite `0.30.0` et un graphe normal sans pile TLS. `alpha.4` ajoute uniquement la feature Tokio `time` au backend afin d'appliquer les deadlines produit. ## Ownership physique retenu ### `game-realtime-transport-lib` Créer sous : ```text crates/common/game-realtime-transport-lib ``` Responsabilités : - contrat transport-neutral ; - payload binaire opaque ; - distinction send/receive/close ; - fermeture distante explicite ; - catégories d'erreur transport-neutral ; - contrat de split permettant lecture et écriture concurrentes ; - aucune dépendance à Tokio, Tungstenite, HTTP, TLS, SDL, Tauri, WASM, moteur ou gameplay. Cette crate est une capability technique commune indépendante d'une génération de moteur. Elle n'est pas placée dans `engine-v1-*`. ### `game-realtime-websocket-lib` Créer sous : ```text crates/common/game-realtime-websocket-lib ``` Responsabilités : - implémentation WebSocket du contrat transport ; - `tokio-tungstenite` et Tokio réseau/temps ; - connexion client WebSocket ; - bind/accept serveur local ; - mapping des frames WebSocket vers le contrat binaire ; - close handshake, erreurs, timeouts, limites et tracing spécifiques au backend ; - tests loopback localhost. Le backend dépend de `game-realtime-transport-lib`. L'inverse est interdit. ### Dépendances interdites Aucune crate sous : ```text crates/games/ crates/engines/ ``` doit dépendre de `tokio-tungstenite` ou du backend WebSocket pendant `0.3.4`. Aucun contrat gameplay n'est déplacé vers les crates transport pour fabriquer artificiellement un consommateur. ## Contrat transport minimal Le contrat public doit rester suffisamment petit pour être implémentable par WebSocket puis confronté au POC WebTransport de `0.3.5`. ### Payload Le message transport est un buffer binaire possédé, conceptuellement `Vec`. Décisions : - aucune variante texte dans l'API transport-neutral ; - aucun JSON, Serde, Protobuf ou codec gameplay dans `0.3.4` ; - Ping/Pong WebSocket reste un détail du backend ; - une frame Text reçue par le backend n'est pas promue en message métier et doit produire un comportement explicite, testé et tracé ; - le futur wire codec consommera/produira ces octets au-dessus du transport. ### Connexion et split Le contrat doit permettre conceptuellement : ```text connection.split() -> sender + receiver sender.send(bytes) -> async Result sender.close() -> async Result receiver.receive() -> async Result ``` `alpha.2` retient une API statiquement dispatchée fondée sur des associated types : `RealtimeConnection` produit un `Sender` et un `Receiver`, puis les opérations async exposent des futures associées GAT (`SendFuture`, `CloseFuture`, `ReceiveFuture`). Le contrat commun n'utilise ni `async-trait`, ni `Box`, ni runtime concret. Aucune borne `Send` n'est imposée aux futures par le contrat transport-neutral : un backend natif peut naturellement fournir des futures `Send`, tandis qu'un futur backend navigateur/WASM ne doit pas être rendu impossible par une contrainte de threading qui ne relève pas du transport abstrait. Le contrat doit supporter lecture et écriture concurrentes après split. Il n'expose pas de runtime Tokio et ne crée aucun executor. ### Fermeture La fermeture distante n'est pas une erreur I/O générique. `receive()` doit pouvoir distinguer au minimum : - message binaire reçu ; - fermeture distante propre ; - erreur de transport. La fermeture locale est explicite via `close()`. Une cancellation de task/future n'est pas assimilée à une fermeture WebSocket propre. Abandonner le propriétaire de la connexion doit néanmoins libérer les ressources sans tâche backend détachée. ### Erreurs Le contrat transport-neutral doit représenter au minimum les catégories suivantes sans exposer les types d'erreur de Tungstenite : ```text invalid endpoint/configuration connect/bind/accept failure timeout message too large backpressure/write buffer full connection closed I/O failure protocol/backend failure cancelled/operation aborted lorsque cette distinction est réellement observable ``` Les détails backend peuvent être conservés pour `Display`/source/tracing sans faire remonter `tungstenite::Error` dans l'API commune. ## Ownership Tokio/runtime Le runtime est possédé par l'application, le service ou le test consommateur. `game-realtime-websocket-lib` : - utilise les primitives Tokio nécessaires ; - ne crée pas de runtime global ; - ne lance pas de thread runtime privé ; - évite les tâches détachées pour la connexion de base ; - ne propage aucune dépendance Tokio dans le gameplay. Les tests peuvent utiliser `#[tokio::test]` comme harness de validation, sans transformer la macro en contrat public. ## Client et serveur La séparation retenue est asymétrique et minimale : - le contrat commun modélise une connexion déjà établie et ses deux moitiés ; - le backend WebSocket possède les constructeurs concrets client/server ; - aucun trait générique `Connector`, `Listener`, `Server`, `Provider` ou `Runtime` n'est créé dans `alpha.2` sans besoin démontré ; - le client peut exposer une connexion à partir d'un endpoint WebSocket ; - le serveur peut binder une adresse socket puis accepter une connexion ; - le futur backend WebTransport pourra proposer ses propres étapes d'établissement tout en produisant une connexion compatible lorsque cela reste pertinent. Cette décision évite de forcer aujourd'hui WebSocket et QUIC à partager artificiellement des opérations d'établissement différentes. ## TLS La baseline `0.3.4` est d'abord un POC transport local reproductible en `ws://`. Aucune feature TLS de `tokio-tungstenite` n'est activée par défaut dans la première implémentation. `wss://` pourra être ajouté si un consommateur direct ou la stratégie de terminaison TLS le justifie réellement. Cette décision : - réduit les dépendances initiales ; - évite de choisir prématurément `native-tls` contre `rustls` ; - n'interdit pas une terminaison TLS future en reverse proxy ; - ne présente pas le WebSocket local de test comme configuration de production Internet. ## Timeouts et backpressure ### Principes - aucune file interne non bornée ; - `send()` attend la capacité du sink au lieu d'accumuler des messages arbitrairement ; - les limites Tungstenite sont configurées explicitement et ne restent pas implicitement aux maxima amont ; - l'absence de message reçu n'est pas un timeout de transport automatique : heartbeat/idle/session timeout appartient à la couche supérieure ; - connect, send et fermeture propre disposent de bornes configurables afin qu'une opération locale ne reste pas bloquée indéfiniment. ### Valeurs de baseline proposées Valeurs initiales, configurables et non assimilées à un protocole gameplay final : ```text max message size 1 MiB max frame size 1 MiB write buffer target 64 KiB max write buffer 2 MiB connect timeout 10 s send timeout 5 s close timeout 2 s receive idle timeout aucun au niveau transport ``` Le choix `2 MiB` pour le write buffer maximum laisse au moins la place au buffer cible plus un message maximal. Les tests négatifs doivent vérifier le comportement limite plutôt que supposer que ces constantes resteront éternellement inchangées. ## Tracing Domaines proposés : ```text games::realtime::transport games::realtime::websocket ``` Le tracing doit couvrir au minimum connexion, accept, fermeture, timeout, rejet de message hors limite et erreurs backend. Ne pas logguer par défaut le contenu brut des payloads de transport. Les futurs domaines session/sync/simulation utilisent des targets distinctes. ## Décisions matérialisées dans `alpha.3` Le backend concret reste sous `crates/common/game-realtime-websocket-lib` et dépend uniquement du contrat commun plus des briques réseau/tracing nécessaires. L'API publique expose : ```text connect(endpoint) WebSocketListener::bind(address) WebSocketListener::accept() WebSocketConnection WebSocketSender WebSocketReceiver ``` Les types `WebSocketStream`, `SplitSink`, `SplitStream`, `MaybeTlsStream` et `tungstenite::Error` restent privés. Client et serveur produisent le même `WebSocketConnection` public grâce à une représentation interne à deux variantes ; le contrat commun reste donc la seule frontière partagée par les consommateurs. `connect()` accepte volontairement uniquement `ws://` dans cette baseline. Le TLS direct reste différé. Les frames binaires deviennent `TransportMessage`; Close devient `TransportReceive::Closed`; Ping/Pong sont traités comme contrôle backend ; Text et raw Frame sont explicitement rejetés comme erreurs de protocole. Les tests négatifs exhaustifs correspondants restent planifiés pour `alpha.4`. Le backend ne crée aucun runtime, thread ni task détachée. Le harness d'intégration utilise un runtime Tokio de test courant, un bind `127.0.0.1:0` et une borne temporelle uniquement pour empêcher un test loopback défectueux de rester suspendu. Aucun `README.md`/`USAGE.md` local n'est ajouté pendant `alpha.3` : la crate reste petite, son API publique est documentée par rustdoc et le présent plan porte encore les décisions durables. Ce choix sera réévalué pendant la consolidation finale conformément à `DOC-CRATE-*`. ## Décisions matérialisées dans `alpha.4` `game-realtime-websocket-lib` expose désormais `WebSocketConfig` et les variantes configurables `connect_with_config()` et `WebSocketListener::bind_with_config()`. Les wrappers historiques `connect()` et `bind()` conservent des valeurs de baseline explicites : ```text max message size 1 MiB max frame size 1 MiB write buffer target 64 KiB max write buffer 2 MiB connect/handshake 10 s send 5 s close 2 s receive idle timeout aucun ``` La configuration refuse une limite nulle, une frame plus grande que le message, un write buffer maximum incapable de contenir le target plus un message maximal et une deadline nulle. Les limites Tungstenite sont transmises aux handshakes client et serveur ; les tailles message/frame sortantes sont également vérifiées avant l'appel au sink afin que le rejet soit déterministe. `connect_timeout` borne la connexion client et la phase de handshake serveur après accept TCP. Le listener reste volontairement capable d'attendre indéfiniment un nouveau peer : ce temps d'attente appartient au service consommateur, pas à une connexion déjà en établissement. `send_timeout` et `close_timeout` bornent leurs opérations respectives. Après expiration d'un `send`, la moitié émission refuse un nouvel envoi avec `Aborted`, car l'appel interrompu peut avoir progressé partiellement et ne doit pas être rejoué implicitement. Aucun idle timeout n'est ajouté à `receive()`. Heartbeat, inactivité joueur et politique de session restent au-dessus du transport. Les tests négatifs `alpha.4` couvrent : - rejet sortant d'un payload hors limite avant écriture ; - rejet entrant d'un message/frame hors limite par Tungstenite ; - rejet d'une frame Text par le contrat binaire ; - drop TCP/WebSocket sans close handshake, attendu comme erreur de protocole ; - timeout de handshake serveur avec un peer TCP silencieux ; - mapping déterministe de `WriteBufferFull` vers `Backpressure` et de `Capacity` vers `MessageTooLarge`. Un test réseau artificiel de saturation `WriteBufferFull` n'est pas retenu : Tungstenite documente que son write buffer ne dépasse le target que lorsque les écritures sous-jacentes échouent, ce qui rendrait une saturation loopback normale non représentative et potentiellement flaky. La borne finie est néanmoins configurée, le mapping backend est testé unitairement et le timeout d'émission borne l'attente du sink. ## Tests retenus ### Contrat commun Tests unitaires ciblés : - types d'erreur et affichage ; - invariants de configuration réellement portés par la crate commune ; - aucune dépendance au backend concret. ### Backend WebSocket Tests d'intégration localhost déterministes avec `127.0.0.1:0` : 1. bind serveur sur port éphémère ; 2. connexion client ; 3. client → serveur : payload binaire ; 4. serveur → client : réponse binaire ; 5. fermeture propre initiée d'un côté et observée de l'autre ; 6. dépassement de taille rejeté ; 7. comportement explicite sur frame Text ; 8. connexion interrompue/peer drop ; 9. timeout d'opération retenu ; 10. backpressure/write-buffer error provoquée par un test borné si elle est reproductible sans test flaky. Les tests ne dépendent ni d'Internet, ni d'un serveur externe, ni d'un port fixe. Un petit launcher de smoke n'était pas prévu par défaut tant que les tests publics semblaient suffire. La revue après validation de `alpha.4` a distingué correctement test automatisé et smoke runtime : `alpha.5` ajoute donc `game-realtime-websocket-smoke` sous `crates/apps/`. Ce binaire reste un harness de validation, pas un produit ni un protocole applicatif. ## Gates par jalon ### `alpha.1` Changements : gouvernance, audit Python, version workspace, plan et delta. Gate utilisateur : ```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 ``` Aucun test runtime réseau n'est requis avant l'existence du code réseau. ### `alpha.2` Ajouter `game-realtime-transport-lib`, ses tests ciblés et sa documentation publique. Gate minimale supplémentaire : ```bash cargo test -p game-realtime-transport-lib --all-targets --all-features ``` ### `alpha.3` Ajouter `game-realtime-websocket-lib`, les dépendances backend et le loopback principal. Gates ciblées : ```bash cargo test -p game-realtime-transport-lib --all-targets --all-features cargo test -p game-realtime-websocket-lib --all-targets --all-features cargo tree -p game-realtime-websocket-lib --edges normal ``` ### `alpha.4` Fermer robustesse, limites, timeouts, close/cancellation et cas négatifs. Réexécuter les tests des deux crates. Un demo n'est ajouté que si une preuve manque réellement. Gates ciblées : ```bash cargo test -p game-realtime-transport-lib --all-targets --all-features cargo test -p game-realtime-websocket-lib --all-targets --all-features cargo tree -p game-realtime-websocket-lib --edges normal ``` Le test backend doit maintenant couvrir le loopback positif, les invariants de configuration, les mappings de capacité/backpressure et les cinq scénarios négatifs déterministes de `robustness.rs`. ### `alpha.5` Ajouter le smoke runtime public avant promotion de maturité. Gates ciblées : ```bash cargo test -p game-realtime-transport-lib --all-targets --all-features cargo test -p game-realtime-websocket-lib --all-targets --all-features cargo run -p game-realtime-websocket-smoke cargo tree -p game-realtime-websocket-smoke --edges normal ``` Le smoke doit binder `127.0.0.1:0`, connecter un client via l'API publique, échanger un payload binaire dans chaque sens, effectuer un close propre, afficher `game-realtime-websocket-smoke: PASS` et sortir avec le code zéro. Il ne dépend ni d'Internet, ni d'un port fixe, ni d'un harness `#[test]`. ### Beta La beta est le jalon large retenu pour : ```bash cargo test --workspace --all-targets --all-features ``` Elle revalide également audits, format/check, Clippy, tests loopback et graphe de dépendances. ### RC La RC gèle le comportement et rejoue les gates de publication utiles. Le test workspace complet n'est répété que si un fix depuis la beta a modifié du Rust, des dépendances ou une frontière transverse qui le justifie. ## Forecast révisé ### `0.3.4-alpha.1` — gouvernance, audit et contrat - migrer les règles prospectives ; - adapter l'audit de version sans casser l'historique ; - synchroniser la version workspace ; - vérifier dépendances et contraintes amont ; - décider ownership, contrat, erreurs, lifecycle, limites et tests ; - créer le présent plan. Aucune dépendance réseau n'est ajoutée. ### `0.3.4-alpha.2` — API transport-neutral - créer `game-realtime-transport-lib` ; - implémenter message, receive/close, erreurs et split ; - conserver l'API sans Tokio/Tungstenite ; - retenir des futures associées GAT afin de préserver le dispatch statique sans box ni contrainte `Send` imposée au contrat ; - tests unitaires ciblés ; - README/USAGE uniquement si une valeur durable est démontrée par `DOC-CRATE-*`. ### `0.3.4-alpha.3` — backend WebSocket et loopback - créer `game-realtime-websocket-lib` ; - ajouter Tokio, `tokio-tungstenite` et `futures-util` avec features minimales ; - client connect + serveur bind/accept ; - mapping binaire, tracing, close ; - round-trip localhost déterministe. ### `0.3.4-alpha.4` — robustesse et consolidation technique - `WebSocketConfig` avec limites et deadlines explicites ; - application symétrique de la configuration aux handshakes client/serveur ; - timeout connect/handshake, send et close, sans idle timeout transport ; - fermeture distante propre conservée et abrupt drop distingué ; - cas Text non supporté ; - message/frame hors limite ; - write buffer maximum borné et mapping `Backpressure` testé sans fabriquer un test réseau flaky ; - lifecycle sans runtime/task backend privé ; - documentation API/backend et audit du graphe. La gate `alpha.4` est propre, mais elle ne constitue pas à elle seule un smoke runtime : tous ses scénarios restent exécutés sous le harness de test Cargo. ### `0.3.4-alpha.5` — smoke runtime end-to-end - créer `game-realtime-websocket-smoke` comme launcher minimal sous `crates/apps/` ; - utiliser uniquement les API publiques `game-realtime-websocket-lib` et `game-realtime-transport-lib` ; - binder un listener loopback sur port éphémère et connecter un client réel ; - vérifier client → serveur puis serveur → client ; - initier et observer un close propre ; - borner le smoke global et retourner un code de sortie non nul au moindre défaut ; - ne créer ni protocole session/gameplay, ni serveur externe, ni port fixe. `DOC-CRATE-*` est réévalué pour ce launcher : le workflow se résume à une commande Cargo unique et le plan central décrit entièrement sa responsabilité, donc aucun `README.md`/`USAGE.md` local n'est ajouté. ### `0.3.4-beta.1` — validation large - aucun nouveau scope ; - test workspace complet planifié ; - tests loopback/negatifs ; - dépendances et frontières vérifiées ; - confirmation qu'aucun gameplay/engine ne dépend du backend WebSocket. Un défaut fermé produit `beta.1.fix.N`. Une capacité manquante réouvre une alpha. ### `0.3.4-rc.1` — candidate gelée - scope gelé ; - `CHANGELOG.md` ; - `ROADMAP.md` si statut macro à réconcilier ; - historique beta ; - documentation finale ; - prompt `0.3.5` préparant WebTransport/QUIC sur la même frontière ; - aucune abstraction nouvelle sans défaut de release. ### `0.3.4` — stable Promotion mécanique : version stable, historique RC, changelog stable, clôture du plan, delta final et ajustement mécanique du prompt suivant. ## Sizing Le scope reste compatible avec une seule version/session : deux petites crates techniques, un seul backend concret et des tests locaux. Le POC WebTransport/QUIC, la session multijoueur et la simulation authoritative sont explicitement exclus. Un split vers une autre version est requis si l'une des conditions suivantes apparaît : - besoin d'un vrai wire codec partagé pour prouver le transport ; - nécessité de concevoir le protocole session/joueur/room ; - TLS direct nécessitant une politique de certificats/PKI produit ; - abstraction commune WebSocket/QUIC exigeant déjà des concepts spécifiques à QUIC ; - demo/app devenant un produit autonome plutôt qu'un harness de preuve. ## Hors scope confirmé - Uroburas Mode 3 ; - matchmaking ; - auth complète ; - snapshots/deltas métier ; - prediction/reconciliation ; - rollback ; - persistence gameplay ; - Redis/NATS/Kafka ; - scaling/sharding/regions ; - WebTransport/QUIC productif ; - Actix Web dans le data plane realtime ; - modification du pipeline Android `0.3.3` sans régression causée par `0.3.4`. ## Critères d'entrée en beta `0.3.4` peut entrer en beta lorsque : - l'API commune ne dépend ni de Tokio ni de WebSocket ; - le backend WebSocket implémente le contrat public sans fuite de types Tungstenite ; - client et serveur round-tripent des payloads binaires sur localhost ; - close local/distant est observable ; - limites et timeouts retenus sont testés ; - aucun queueing non borné ni runtime privé n'est introduit ; - les targets tracing transport/WebSocket sont distinctes des futurs domaines session/sync/simulation ; - aucune crate gameplay ou engine ne dépend de `tokio-tungstenite` ; - les audits et tests ciblés sont propres ; - le smoke `cargo run -p game-realtime-websocket-smoke` passe réellement côté utilisateur hors harness de test ; - la documentation décrit ce qui est réellement implémenté et ce qui reste reporté.