L'adaptateur traduit clavier et manette en actions SEQUA (PlayerInput) ; le Core ne recoit et n'enregistre que ces actions, le rejeu reste exact. - SEQUA 3D : PlayerInput, poll_input(), capacite player_input (interface des adaptateurs en version 2) - protocole Core - rendu 0.2 : render/input, envoye quand les actions changent ; entree remise au neutre si le rendu disparait - Godot : InputMap du projet (ZQSD, fleches, espace, manette) ou script SequaInput.sequa_player_input() pour une correspondance avancee ; entree neutre sans focus ; exemple examples/sequa_input.gd - adaptateur debug : entrees scriptees par numero d'image - Studio : ligne "Entrees du joueur" du panneau Rendu
9.2 KiB
Spécification : protocole Core - rendu 0.2 et sequa-render
- Statut : brouillon (étape 4). Décisions : ADR 0009 ; entrées du joueur (0.2) : ADR 0011.
- Historique : 0.1 (scène, mesures) ; 0.2 : message
render/input, capacitéplayerInput, interface des adaptateurs en version 2. Le fichier garde son nom d'origine. - Le format reste du JSON provisoire (ADR 0005) en attendant l'ADR du schéma. Enveloppe, entiers 64 bits en hexadécimal et règles d'erreur : comme le protocole Studio - Core 0.2 §2 et §3.
- Toute ambiguïté se règle en modifiant CE document.
1. Acteurs
| Acteur | Exécutable / dossier | Rôle |
|---|---|---|
| Core | sequa-core (core/) |
Serveur WebSocket ; lance et supervise le rendu ; projette le monde en deltas SEQUA 3D |
| Rendu | sequa-render (render/) |
Client WebSocket du Core ; charge une bibliothèque d'adaptateur ; applique les deltas ; rend les images |
| Adaptateur | sequa_godot_adapter, sequa_debug_adapter (bibliothèques partagées) |
Implémentent I3DEngineAdapter (3d/) |
| Studio | studio/ |
Demande au Core de démarrer ou d'arrêter le rendu (protocole 0.2, §6) |
2. Lancement de sequa-render
Le Core lance sequa-render avec :
| Option | Effet |
|---|---|
--core <url> |
URL WebSocket du Core, jeton inclus (ws://127.0.0.1:9100/?token=…) |
--adapter <chemin> |
Bibliothèque d'adaptateur à charger |
--adapter-options <json> |
Réglages propres à l'adaptateur, transmis tels quels (ex. sortie vidéo de l'adaptateur de débogage) |
--width <n>, --height <n> |
Taille de rendu (défaut 1280 × 720) |
--headless |
Rendu sans fenêtre (refusé par un adaptateur qui ne le sait pas) |
--fps <n> |
Cadence imposée par sequa-render (0, défaut : celle du moteur, synchronisation verticale comprise). Sert à l'adaptateur de débogage, qui n'a pas de synchronisation verticale |
--lease-grace-ms <n> |
Délai avant arrêt quand la connexion au Core disparaît (défaut 3000) |
Codes de sortie : 0 arrêt normal, 1 erreur de démarrage (connexion, adaptateur, moteur), 2 arguments invalides, 3 bail expiré, 4 fenêtre fermée par l'utilisateur.
Le Core lie le processus de rendu au sien (job object sous Windows, PR_SET_PDEATHSIG sous Linux) : il ne lui survit jamais.
3. Bibliothèque d'adaptateur
Chaque adaptateur exporte (liaison C) :
// Renvoie nullptr si interface_version diffère de la version compilée dans l'adaptateur
// ou si options_json est invalide ; error (facultatif) reçoit alors un message.
sequa::three_d::I3DEngineAdapter* sequa_create_engine_adapter(std::uint32_t interface_version,
const char* options_json,
char* error, std::size_t error_size);
void sequa_destroy_engine_adapter(sequa::three_d::I3DEngineAdapter* adapter);
interface_version = sequa::three_d::adapter_interface_version (entier, 1 au départ, 2 depuis poll_input), incrémenté à chaque changement de I3DEngineAdapter ou des types du contrat. Le rendu détruit l'adaptateur, décharge sa bibliothèque, puis seulement ensuite tout ce que l'adaptateur aurait chargé lui-même (le moteur, déchargé par l'adaptateur à sa destruction).
4. Poignée de main
- Connexion avec le jeton (protocole 0.2 §3).
- Rendu → Core :
hello{ protocolVersion: "0.2.0", client: "sequa-render" }. Le Core reconnaît le canal de rendu àclientet applique la version de CE protocole (compatibilité : même majeure et, en 0.x, même mineure). - Core → rendu :
welcome{ protocolVersion, coreVersion, instanceId, renderId }(renderId: identifiant de ce lancement, 32 hex). - Rendu → Core :
render/ready{ capabilities: { engineName, engineVersion, buffers: [noms §5.6], windowed, headless, playerInput }, width, height }(playerInput: le moteur capte les entrées du joueur, facultatif, faux par défaut) aprèsstart()réussi de l'adaptateur. En cas d'échec :render/failed{ code, message }puis fermeture et code de sortie 1. - Core → rendu :
scene/deltaavecfull: true(scène complète), puis les deltas suivants.
Un seul canal de rendu à la fois : une deuxième connexion sequa-render reçoit error { code: "render-exists" } et est fermée.
5. Messages
Core → rendu
| Type | Payload |
|---|---|
scene/delta |
{ worldVersion: hex, tick: hex, full: bool, sentAtUs: hex, commands: [commande…] } |
render/stop |
{} : le rendu arrête l'adaptateur et sort avec le code 0 |
full: true : le rendu ajoute en tête du delta un destroy pour chaque objet qu'il connaît encore (il garde son propre SceneState), puis l'applique d'un bloc. worldVersion ne recule jamais, même quand la session du Core redémarre : c'est un compteur du Core, distinct du tick. sentAtUs : horloge monotone du Core en microsecondes (steady_clock, commune aux processus d'un même poste sous Windows et Linux), pour la mesure du délai.
Commandes (champ op) ; identifiants d'objets en hexadécimal, nombres en JSON :
op |
Champs |
|---|---|
create |
id, transform, visual |
destroy |
id |
transform |
id, transform |
visual |
id, visual |
camera |
camera |
transform:{ position: [x, y, z], rotation: [x, y, z, w], scale: [x, y, z] }visual:{ primitive: "box" | "sphere" | "capsule" | "plane", size: [x, y, z], color: [r, g, b, a] (linéaire), semanticClass: int }camera:{ transform, verticalFovDeg, near, far }
Rendu → Core
| Type | Payload |
|---|---|
render/frames |
{ frames: int, lastWorldVersion: hex, cpuMsMedian, cpuMsP95, latencyUsMedian, latencyUsP95, applyUsMedian, applyUsP95 } : envoyé toutes les secondes. latencyUs = réception du delta − sentAtUs (transport seul : c'est le critère de l'ADR 0009) ; applyUs = application du delta − sentAtUs (attente de la prochaine image comprise) |
render/failed |
{ code, message } : l'adaptateur a échoué ; fermeture, code de sortie 1 |
render/closed |
{} : l'utilisateur a fermé la fenêtre ; fermeture, code de sortie 4 |
render/input |
{ buttons: int [0, 2^32-1], moveX: int, moveY: int ([-32767, 32767]), capturedAtUs: hex } : entrées du joueur déjà traduites en actions par l'adaptateur (§5.1) |
Une erreur d'application d'un delta (version qui recule, id inconnu) est une erreur de protocole : render/failed { code: "invalid-delta" }.
5.1 Entrées du joueur (0.2, ADR 0011)
- Après chaque image, si l'adaptateur déclare
playerInput, le rendu appellepoll_input()et envoierender/inputseulement si les actions ont changé depuis le dernier envoi (départ : entrée neutre, rien n'est envoyé tant que le joueur ne fait rien). - La traduction des appareils (clavier, manette…) en actions appartient à l'adaptateur. Hors focus, l'adaptateur renvoie l'entrée neutre.
- Le Core applique
render/inputcommesession/input(protocole Studio - Core §4) : prochain tick exécuté, entrée maintenue, la dernière reçue gagne, qu'elle vienne de Studio ou du rendu. Elle est enregistrée : le rejeu est exact. - Payload invalide :
error{ code: "invalid-payload" }, entrée inchangée, connexion conservée.render/inputreçu sur une connexion Studio :errorunknown-type. - Déconnexion du rendu alors que sa dernière entrée n'était pas neutre : le Core remet l'entrée au neutre.
capturedAtUs: horloge monotone du rendu à la lecture (commesentAtUs), pour la mesure future de la latence entre la touche et l'affichage.
6. Ajouts au protocole Studio - Core (0.3)
| Requête | Payload envoyé | Réponse |
|---|---|---|
render/start |
{ adapter: "godot" | "debug", width?, height?, headless?, adapterOptions? } |
état du rendu |
render/stop |
{} |
état du rendu |
render/status |
{} |
état du rendu |
État du rendu : { state: "stopped" | "starting" | "running" | "restarting", adapter?, pid?, restarts, capabilities?, frames?, latencyUsP95? }. Le Core résout adapter en chemin de bibliothèque à côté de sequa-render.
7. Supervision
- Lancement :
render/startou--render <adaptateur>. Le Core attendrender/ready(20 s max, le démarrage de Godot prend 1,5 à 2 s). - Plantage ou déconnexion inattendue : relance après 1 s, au plus 3 fois par minute ; au-delà, état
stoppedet erreur visible dansrender/status. render/closed(fenêtre fermée par l'auteur) : pas de relance, étatstopped.- Arrêt du Core :
render/stop, attente de la fin (3 s), sinon terminaison forcée.
8. Rythme
- Le Core envoie un
scene/deltaaprès chaque tick qui change la scène. Un tick sans changement n'envoie rien. - Le rendu reçoit sur le fil du client, empile, et applique tous les deltas reçus avant chaque image, dans l'ordre, puis appelle
render_frame(). Pas d'interpolation en 0.1 (ADR 0009). - Si le rendu prend du retard, les deltas s'accumulent mais aucun n'est sauté : la scène reste exacte.
9. Hors 0.2
Tampons de rendu (étape 5), mesures de VRAM, plusieurs rendus simultanés, interpolation, plusieurs joueurs, souris et visée.