Manuel de vectra-sdk

Version 0.12.0 · moteur VECTRA 7.9.15 (contrat 1, mode provisoire) · TRUvector

vectra-sdk sert à créer des scènes, des applications et des jeux avec le moteur VECTRA, dans le navigateur. Ce manuel explique tout ce que le kit sait faire aujourd'hui, comment s'en servir, et ce qui n'est pas encore fait. La liste exhaustive des nœuds, matières, comportements et noms anglais est dans la Référence, produite à partir du code.


1. Prendre en main

1.1 Ce qu'il faut

1.2 Installer

git clone git@github.com:truvector/vectra-sdk.git
cd vectra-sdk
npm install
npm run lier-moteur -- ../vectra        # moteur/ devient un lien vers ton dépôt du moteur
npm run dev                             # http://127.0.0.1:8080/

Ouvre ensuite http://127.0.0.1:8080/exemples/01-premier-monde.html, ou la Forge en ligne : http://127.0.0.1:8080/packages/forge-web/.

1.3 Un premier monde

import { Vectra, sdf, mat } from "./vectra/vectra.js";

const monde = await Vectra.creer("#ecran", { ciel: "heure-bleue" });

monde.ajouter(sdf.terrain({ relief: 4 }), mat.pierre());
const phare = sdf.boite([4, 18, 4]).arrondir(0.3)
  .moins(sdf.cylindre(1.2, 20))
  .fondre(sdf.sphere(3).en([0, 10, 0]), 0.8);
monde.ajouter(phare, mat.metal({ teinte: "#00D4FF" })).en([0, 9, -30]);
monde.repeter(sdf.sphere(0.5), { case: 6, hasard: 0.8, sur: "sol" }, mat.verre());
monde.joueur({ marche: true });

Ces quelques lignes donnent un terrain de pierre, un phare de métal creusé d'un puits, mille galets de verre posés sur le sol (calculés, pas stockés) et un joueur qui marche (ZQSD ou WASD, souris, manette, tactile). C'est l'exemple exemples/01-premier-monde.html.

1.4 Les trois portes d'entrée

PortePour quiOù
Le codequi écrit du JavaScriptvectra.js (français) ou vectra.en.js (anglais)
La Forge en lignequi préfère la sourispackages/forge-web/
Les gabaritsqui veut partir d'un projet completnpx vectra nouveau mon-jeu --gabarit plateforme

Et pour essayer sans rien installer : le terrain de jeu (docs/terrain.html, en ligne sur /docs/terrain.html) — du code à gauche, le monde à droite, des exemples, et un lien « Partager » qui contient le code.

Ce qu'on fait par une porte s'ouvre par les deux autres : la Forge enregistre des fichiers .vectra que le code charge avec monde.charger(), et un projet en code s'exporte avec monde.exporter().


2. Comprendre

2.1 Une scène décrite, pas écrite

Dans le moteur, chaque monde est un shader WGSL écrit à la main. Avec le SDK, tu décris ton monde : des formes, des opérations entre elles, des matières, des répétitions. Le SDK en tire, à chaque changement de structure :

  1. le WGSL de la scène (le champ de distance, autour d'une « scène gabarit » qui respecte le contrat du moteur) ;
  2. le GLSL ES 3.0 pour les appareils sans WebGPU (traduction par naga, embarqué dans vendor/naga-wasm/) ;
  3. le même champ en JavaScript, pour le sol, les collisions et la visée.

Une seule description : le sol sur lequel on marche est exactement celui qu'on voit.

2.2 Structure et réglages

C'est la règle la plus utile à connaître :

Conseil : si un objet va tourner plus tard, crée-le avec une rotation (.tourner(0) ou l'option { tourne: true } de ajouter). Les entités en ont toujours une.

2.3 Le bloc auteur et sa limite

Avec le moteur 7.9.15, le bloc auteur loge dans la fin du tampon de jeu du moteur, inutilisée : 132 vec4. Chaque matière en prend 2, chaque objet placé 1 (2 s'il tourne), chaque paramètre un quart ou un entier, et chaque groupe « lourd » 1 de plus pour sa sphère englobante. Les exemples en utilisent entre 9 et 112.

Quand le bloc est plein, le SDK ne casse rien : les derniers paramètres sont écrits en dur dans le shader, et les changer recompile. L'événement avertissement le signale. Le moteur 8.4 donnera une liaison à part de 256 vec4.

2.4 Le moteur, une dépendance

Le SDK ne parle au moteur que par un seul fichier, packages/sdk/src/moteur.js (l'adaptateur). Avec le moteur 7.9.15, qui n'a pas encore de porte publique, l'adaptateur fait lui-même ce que la V8 du moteur apportera : assembler les // #import (portage exact de la Forge, vérifié à l'octet sur 33 sorties), loger le bloc auteur, porter la scène gabarit. Ces contournements sont dans packages/sdk/src/provisoire/ et marqués PROVISOIRE : ils disparaîtront lot après lot. monde.infos.provisoire vaut true tant qu'ils servent.


3. L'API

L'API existe en français (vectra.js) et en anglais (vectra.en.js), à égalité : mêmes objets, mêmes méthodes. La correspondance complète est dans la Référence.

3.1 Vectra

MembreRôle
await Vectra.creer(cible, options)Crée un monde dans un élément ("#ecran", un div ou un canvas). Renvoie un Monde.
Vectra.configurer({ moteur: { racine }, langue })Où est le moteur (si ce n'est pas moteur/ à côté du SDK), langue des messages ("fr", "en").
Vectra.capacites(){ webgpu, webgl2, tactile, recommande }.
await Vectra.moteur()Version du moteur chargé, contrat, mode provisoire, capacité du bloc auteur.
Vectra.scenes, Vectra.ciels, Vectra.noeudsLes listes disponibles.
Vectra.versionVersion du SDK.

Options de creer :

OptionValeursDéfaut
sceneune scène livrée : "plaine", "cathedrale", "organismes"… (voir 3.12)aucune : scène décrite
ciel"jour", "heure-bleue", "aube", "studio", "nuit""jour"
styleun style du moteur ("manga", "vitrail"…)aucun
qualite0 à 3, ou "auto""auto"
mode"auto", "complet", "allege", "fixe" (image fixe : le monde se rend jusqu'à être net puis s'arrête ; un geste, un changement ou capture() le relance)"auto"
graine8 signes (alphabet des graines VECTRA)"A7K2M9QX"
loindistance de vue (m)400
rendu"webgl2" pour forcer le repliWebGPU d'abord
cinemapost-traitement, styles, anti-crénelagetrue
langue"fr", "en""fr"
moteur{ racine: "https://…/moteur/" }moteur/ à côté du SDK
pixelsMaxplafond du rapport de pixels2

3.2 Construire : ajouter, repeter, entite

const tour = monde.ajouter(sdf.cylindre(1, 8), mat.pierre()).en([0, 4, 0]);
monde.repeter(sdf.cone(0.4, 1.2), { case: 4, densite: 0.6, sur: "sol", graine: "sapins" }, mat.bois());
const orbe = monde.entite({ forme: sdf.sphere(0.5), matiere: mat.lueur("#FF4FD8"), position: [2, 1, 0], avec: [comportements.flotter()] });

3.3 Les formes : sdf et l'API chaînée

Formes : sdf.sphere, boite, cylindre, tore, capsule, cone, octaedre, plan, terrain, objet, wgsl, union. Pour une capture en gaussiennes 3D, voir monde.splats() (3.13).

Chaque forme se chaîne :

MéthodeEffet
.en([x, y, z]), .tourner([rx, ry, rz]) ou .tourner(degrés), .echelle(s)placement
.union(...), .moins(...), .inter(...), .fondre(autre, k), .morphose(autre, t)opérations (morphose : 0 = cette forme, 1 = l'autre, réglable sans recompiler)
.arrondir(r), .creuser(e), .tordre(f), .plier(f), .onduler(a, f), .miroir("x"), .bruit(amplitude, frequence, graine), .fractale({ iterations, echelle, decalage })modificateurs (bruit : surface rugueuse ; fractale : repliements successifs, façon Menger)
.grille(pas, nombre), .parCase({ … }), .anneau(n, rayon)répétitions
.matiere(mat.x()), .nommer("Porte")matière, nom

Une opération prend la forme sur laquelle elle est appelée comme premier enfant : a.moins(b) creuse b dans a.

3.4 Les matières : mat

mat.pierre(), bois(), platre(), metal(), verre(), gelee(), lueur(), tissu(), terre(), eau(), mat(), objet(). Options : teinte ("#rrggbb", ou mat.lueur("#F5C542") directement), rugosite, metal, emission, texture. La pierre, le bois, le plâtre, le métal, le tissu et la terre prennent les textures photographiées du moteur (CC0), teintées par la couleur choisie. Le verre, la gelée et l'eau ont leur propre éclairage.

3.5 Objet et Entite

MembreEffet
.en(p), .tourner(r), .echelle(s)placement, sans recompiler
.position, .rotation, .centre, .rayon, .visible, .echelleActuellelecture
.regler({ rayon: 2 })paramètres de la forme racine
.matiere.regler({ teinte: "#f00" })matière, sans recompiler
.cacher(), .montrer(), .retirer()visibilité ; retirer ôte la forme du graphe peu après
entite.avec(comportement…), .sans(nom)comportements
entite.retirer({ effet: "eclat", son: "cloche" })retrait animé
entite.donneestes propres données

3.6 Le joueur et la caméra

const joueur = monde.joueur({ marche: true, saut: 7.5, vitesse: 4.5, position: [0, 2, 10] });
joueur.position = monde.surLeSol([5, 1.7, 5]);

Le corps a deux sphères (pieds et tête), la gravité, le saut, la course (Maj). Dans une scène décrite, il est repoussé hors du champ de distance ; dans une scène livrée, il suit le sol et les collisions de la scène, comme dans le lecteur. marche: false : vol libre (R et F pour monter, descendre).

Caméra (monde.camera) :

MéthodeMode
libre()vol libre, glisser pour regarder (mode par défaut sans joueur)
orbite(cible, { distance, hauteur, auto, vitesse })tourner autour d'un objet ou d'un point
suivre(entite, { distance, hauteur })à la troisième personne
poser(position, regard)pose fixe
joueur()première personne : les yeux du joueur (mode par défaut dès qu'il y a un joueur)
await allerVers(cible, { duree, distance })vol animé (raccourci : await monde.allerVers(cible))
rail(points, { vitesse, duree, boucle, regard, avance })sur rail : parcourt les points à vitesse constante, en regardant devant (ou regard)
sequence({ cles, legendes, duree, boucle })mise en scène, format des séquences du moteur : [instant, x, y, z, visé x, y, z]

monde.joueur({ lacet }) oriente le joueur au départ (radians ; Math.PI / 2 regarde vers +x).

3.7 Les actions

Les entrées sont ramenées à des actions nommées : avancer, reculer, gauche, droite, sauter, courir, interagir, lanterne, monter, descendre, pause. Les touches sont données par position : la touche W d'un clavier QWERTY est la touche Z d'un AZERTY, sans rien régler.

monde.actions.sur("interagir", () => porte.retirer());
monde.actions.lier("style", ["KeyV"]);          // une action à toi
monde.actions.etat("courir");                    // maintenue ?
monde.actions.capturerSouris();                  // pointeur verrouillé (jeux à la première personne)

Tactile : la moitié gauche de l'écran est un joystick, la droite le regard ; une tape est un clic. Manette : disposition standard.

3.8 Les règles et les événements

monde.quand(joueur).touche(cristaux, (cristal) => cristal.retirer({ effet: "eclat" }));
monde.quand(joueur).entre({ centre: [0, 1, -20], rayon: 3 }, () => monde.ui.message("Le seuil"));
monde.quand(porte).clic(() => porte.cacher());
monde.chaque(1, () => …); monde.apres(10, () => …);
monde.on("image", (dt) => …);

Contacts : touche, quitte ; zones : entre, sort ; clic ; regard : vise(objets, fn, { distance }) — le centre de la vue se pose sur l'un d'eux (une fois par regard) ; chaque(s, fn), apres(s, fn). Chaque règle renvoie de quoi la retirer ; monde.off(nom, fn) retire un écouteur posé par monde.on. Événements de on : pret, premiere-image, image, avant-image, clic (avec { point, distance, objet }), compile, qualite, lanterne, fin, erreur, avertissement, sequence-finie, porte, interrupteur, collecte, collecte-finie.

Une erreur dans ton code n'arrête jamais le monde : elle est attrapée, signalée par l'événement erreur (une ErreurVectra qui donne la ligne), et le reste continue. Une règle ou un comportement fautif trois fois est coupé (avec un avertissement).

3.9 Les comportements

comportements.tourner, flotter, patrouiller, orbiter, suivre, fuir, troupeau, pulser, minuterie, et pour les jeux :

ComportementCe qu'il fait
porte({ deplacement, vitesse, ouverte })l'entité glisse de sa place (fermée) de deplacement (ouverte) ; elle reçoit ouvrir(), fermer(), basculer(), ouverte
interrupteur({ cible, mode, distance, action, sur, poids })mode: "basculer" : près de lui, l'action « interagir » (E, bouton X) le bascule ; mode: "plaque" : actif tant que le joueur (ou une entité de poids) se tient dessus ; il ouvre ou ferme cible et appelle sur(actif)
collecte({ compteur, total, effet, son, valeur })ramassé au contact du joueur ; le compteur avance (affiché si total) ; événements collecte et collecte-finie
compteARebours(secondes, fin, { libelle })une minuterie affichée ; fin() à zéro
const porte = monde.entite({ forme: sdf.boite([3, 3, 0.4]), matiere: mat.bois(), position: [0, 1.5, -10], avec: [comportements.porte()] });
monde.entite({ forme: sdf.cylindre(0.4, 0.3), matiere: mat.metal(), position: [3, 0.15, -6], avec: [comportements.interrupteur({ cible: porte })] });
monde.entites(5, (i) => ({ forme: sdf.octaedre(0.5), matiere: mat.lueur("#F5C542"), position: [i * 2 - 4, 1, -3], avec: [comportements.collecte({ compteur: "Cristaux", total: 5 })] }));
monde.on("collecte-finie", () => monde.fin("Bravo !"));

Signatures complètes dans la Référence. Un comportement est un objet { nom, attacher?(entite, monde), maj(entite, dt, monde, t) } : tu peux écrire les tiens. Trois jeux complets n'utilisent que cette API : exemples/02-jeu-collecte.html, 09-course.html (anneaux dans l'ordre, compte à rebours), 10-enigme.html (trois leviers, une porte, une clé).

3.10 L'interface de jeu

monde.ui.compteur(nom, valeur, max?), monde.ui.minuterie(nom, secondes, fin), monde.ui.message(texte, secondes), monde.fin(titre, { texte, rejouer }), monde.ui.effacer(). Du HTML par-dessus l'image, aux couleurs TRUvector, lisible par les lecteurs d'écran.

3.11 Styles, lumière, son, ancres

3.12 Les scènes livrées

Vectra.creer("#ecran", { scene: "plaine" }) monte une scène du moteur : organismes, cathedrale, abysse, aurore, atelier, genese, maison, plaine, ville. On y marche (monde.joueur()), on y change de style, on y pose des règles de zones et une interface. On ne peut pas y ajouter de formes avec le moteur 7.9.15 (leur shader est fermé) : pour un monde à toi, pars d'une scène décrite (un sdf.terrain, par exemple).

3.13 Les objets importés

const avocat = await monde.objet("avocat.vobj");             // livré avec le moteur
monde.ajouter(avocat, mat.objet()).echelle(1.6).en([0, 0.8, 0]);
const mien = await monde.objet(fichierGlb);                   // un .glb à toi, converti dans le navigateur

monde.objet() accepte un nom de web/objets/ du moteur (avocat.vobj, bouteille.vobj), un .vobj, ou un .glb (glTF 2.0 binaire) qu'il convertit en champ de distance dans un Worker (la page ne gèle pas). mat.objet() reprend les couleurs du modèle (texture comprise). Avec le moteur 7.9.15, un seul objet importé par scène.

Splats (gaussiennes 3D, lot 0.7) : const n = await monde.splats("flacon-splats.vspl", { en: [0, 1, -3], echelle: 1.5, rotation: 30, opacite: 1 }) pose un nuage de splats — un .vspl livré par le moteur, ou un .ply / .spz / .vspl à toi (File, ArrayBuffer) — dessiné par le rendu de splats du moteur après la scène, caché par ce qui est devant lui. Le résultat se déplace (n.en(p), n.tourner(deg), n.echelle(s), n.opacite(v)) et se retire (await n.retirer()). Un nuage par monde ; visuel seulement (pour des collisions, pose une forme simple à sa place).

Un objet importé voyage avec le projet : monde.exporter() range ses octets .vobj (en base64) dans projet.objets, et monde.charger() les relit ; un objet livré par le moteur se recharge simplement par son nom.

3.14 Les projets .vectra

monde.exporter({ nom, script, sequence }) renvoie un projet (format: "vectra-projet", version: 1) : graphe, ciel, style, caméra, joueur, script, langage ("js" ou "ts") et objets (les objets importés). await monde.charger(projet) le recharge, et exécute son script. Qui modifie monde.graphe à la main (comme la Forge) prévient le monde par monde.majGraphe({ structure }). await monde.executer(code) exécute un script d'auteur : il reçoit monde, sdf, mat, comportements, Vectra (et world, behaviours).

Scripts en TypeScript. monde.executer(code, nom, { typescript: true }) — ou un script dont la première ligne est // @ts, ou un nom en .ts — retire d'abord les types, comme le fait Node 22 : le JavaScript restant s'exécute tel quel, aux mêmes lignes (les erreurs pointent la bonne ligne). Sont retirés : annotations de variables, de paramètres (x?: T compris) et de retours, as T, satisfies T, x!, interface, type X = …, declare, génériques des fonctions déclarées, import type, modificateurs de classe (private, readonly…). Comme Node, enum, namespace, paramètres promus et décorateurs sont refusés avec un message clair. Il n'y a pas de vérification des types : pour cela, ton éditeur et les déclarations de npm run types.

// @ts
interface Score { points: number }
const s: Score = { points: 0 };
monde.quand(joueur).touche(cristal, (c: Entite) => { s.points++; c.retirer(); });

3.15 Le multijoueur

const salon = await monde.reseau.salon({ api: "https://vectra.truvector.net", salon: "PRJ-K7M2QX9A", nom: "Ana" });
salon.sur("arrivee", (j) => monde.ui.message(`${j.nom} arrive`));
salon.partager("porte", "ouverte"); salon.score(42); salon.evenement("tir", { x: 1 });

Chaque joueur envoie sa position dix fois par seconde ; les autres apparaissent comme des silhouettes lumineuses, lissées. Le salon (un Durable Object par nom) relaie aussi un état partagé, des événements et un classement. joueurs: 8 fixe la taille du salon (seize au plus) ; le suivant est refusé.

Entités partagées (lot 0.10) : salon.partagerEntite(entite, { cle }) — sa position et sa rotation partent vingt fois par seconde de chez son propriétaire, et les autres la suivent, lissée. Le salon fait autorité : le premier qui l'annonce la possède, les mises à jour des autres sont refusées ; salon.prendre(entite) la reprend (on la pousse), salon.lacher(entite) la libère, salon.possede(entite) le dit ; un joueur qui part libère les siennes. Une entité retirée (ramassée) l'est pour tous. Voir la balle de exemples/07-multijoueur.html.

3.16 Interroger le monde

monde.distance(p), monde.solSous(x, z), monde.surLeSol([x, y, z]), monde.viser(u, v) (rayon depuis la caméra), monde.hasard(graine) (.suivant(), .entre(a, b), .disque(r), .choisir(liste)), monde.infos.

monde.infos dit aussi où la traduction WebGL2 s'est faite (glsl: "worker" : dans un Worker, la page ne gèle pas). await monde.sonder([[x, y, z], …]) (anglais : probe) demande à la carte graphique la distance et la normale en 8 points au plus : [{ distance, normale }], une à deux images plus tard. Elle voit exactement ce que le rayon voit, nœud WGSL libre compris. Elle passe par la sonde cs_probe du moteur : WebGPU seulement (en WebGL2, ou dans une scène livrée, elle renvoie null).


4. Le graphe de scène

Le format graphe.v1 est ce que la Forge enregistre et ce que l'API chaînée écrit :

{
  "format": "vectra-graphe", "version": 1,
  "noeuds": {
    "n1": { "type": "terrain", "p": { "relief": 4 }, "matiere": "m1" },
    "n2": { "type": "sphere", "p": { "rayon": 3 }, "en": [0, 10, 0] },
    "n3": { "type": "fondre", "p": { "k": 0.8 }, "enfants": ["n4", "n2"] }
  },
  "racine": ["n1", "n3"],
  "matieres": { "m1": { "base": "pierre" } }
}

Règles : chaque nœud a au plus un parent ; les racines forment une union ; une matière s'hérite du parent ; un seul objet importé par graphe (moteur 7.9.15). npx vectra verifier fichier.vectra valide un fichier ; les erreurs nomment le nœud, en français et en anglais.

Le compilateur (packages/graphe/src/compiler.js) : valide, sépare la structure des paramètres (l'empreinte de la structure décide s'il faut recompiler), calcule les boîtes englobantes et les facteurs de sûreté des déformations, range les paramètres dans le bloc auteur (placements d'abord, puis sphères englobantes, puis paramètres de forme), puis émet le WGSL et l'évaluation JavaScript. Les enfants « lourds » d'une union sont élagués par leur sphère englobante : le rayon ne les calcule que s'il passe près. L'évaluation JavaScript élague exactement de la même façon : la sonde GPU et le JavaScript s'accordent à mieux que 1 mm (contrôlé sur 1 000 points près des surfaces, toutes sortes de nœuds).

Le nœud WGSL libre (sdf.wgsl(code, borne)) prend du code d'expert (il reçoit p et renvoie une distance). Une erreur dans ce code est ramenée au nœud et à la ligne de l'auteur (erreur wgsl-libre : « WGSL libre « Mon champ », ligne 2 : unresolved call target 'lenght' »), et l'image d'avant reste affichée. Il n'a pas d'équivalent JavaScript : pour les collisions du joueur, le SDK interroge alors la sonde GPU du moteur à chaque image (pieds, tête, sol juste dessous, un pas en avant) et corrige le champ JavaScript avec ce qu'elle voit. Le joueur attend que la sonde ait vu la scène compilée avant de tomber. WebGPU seulement : en WebGL2 le joueur traverse encore ces formes. Voir exemples/08-wgsl-libre.html.


5. La Forge en ligne

packages/forge-web/index.html. Elle est écrite avec le SDK et n'utilise que son API publique.


6. La balise <vectra-monde>

<script type="module" src="https://vectra.truvector.net/sdk/0/element.js"></script>
<vectra-monde scene="plaine" data-style="manga" joueur></vectra-monde>
<vectra-monde projet="https://p.truvector.net/p/PRJ-K7M2QX9A.vectra" joueur></vectra-monde>

Attributs : scene, projet, ciel, data-style (style est déjà un attribut HTML), graine, qualite, joueur, orbite, rendu, langue, moteur. Événements DOM : vectra-pret (avec le monde), vectra-erreur.


7. L'outil en ligne de commande

CommandeEffet
vectra nouveau <dossier> --gabarit <nom>un projet depuis un gabarit, avec sa copie du SDK et son vectra.json
vectra nouveau <dossier> --depuis monde.vectraun projet qui joue un fichier de la Forge (gabarit projet)
vectra dev [dossier] --port 8080sert le projet et le moteur en local
vectra verifier <fichier>valide un .vectra ou un graphe (--en : messages anglais)
vectra construire [dossier] --sortie distun site statique autonome (SDK + moteur, ≈ 14 Mo), vérifié pour Cloudflare Pages (aucun fichier > 25 Mo)
vectra publier <fichier.vectra> --api <url> --jeton <jeton>envoie et publie ; affiche le lien et la balise
vectra moteurquel moteur est lié, et où

Noms anglais : new, check, build, publish, engine, help.

Les gabarits

GabaritCe qu'il montre
plateformesauter de pilier en pilier jusqu'au phare ; chutes comptées, retour au départ
visiteune place et des pavillons ; un clic vole jusqu'à un pavillon et ouvre sa fiche
vitrineun objet en orbite, une teinte réglée depuis la page
expositionune galerie à parcourir ; les cartels s'affichent à l'approche des œuvres
bac-a-sablevoler et poser des formes d'un clic
clipune mise en scène : clés de caméra, légendes, styles qui changent, musique
projetjoue un fichier projet.vectra (celui de la Forge), avec sa mise en scène de départ s'il en a une ; c'est le gabarit de l'export .zip et de l'export GitHub

Chaque gabarit porte .github/workflows/deployer.yml : à chaque poussée sur main, il récupère le moteur (dépôt privé, jeton MOTEUR_LECTURE) à la version de vectra.json, et déploie sur Cloudflare Pages.


8. Le nuage : Cloudflare

Le Worker vectra-projets (api/) est séparé de vectra-api du moteur, qui ne change pas. Tout tient dans l'offre gratuite.

RouteRôle
GET /api/santeétat
POST /api/comptescompte anonyme → { id, jeton }
GET /api/auth/github → /api/auth/github/retourconnexion GitHub (facultative ; droits read:user repo workflow, pour « Exporter vers GitHub ») ; retour vers /forge (ADRESSE_FORGE)
POST/GET /api/projets, GET/PUT/DELETE /api/projets/:idprojets privés, versionnés (R2 + index D1)
POST /api/projets/:id/publiercopie figée, code PRJ-XXXXXXXX
GET /p/:code.vectra, GET /p/:codele projet publié et sa page publique
GET /api/galerie, POST /api/signalergalerie ; trois signalements masquent une publication
GET /api/salons/:nom (WebSocket)salon multijoueur (Durable Object SalonProjet)

Garde-fous : projets de 8 Mo au plus (objets importés compris ; TAILLE_MAX), noms et contenu des objets embarqués vérifiés, 60 envois par heure et par compte, état des salons assaini (nombres, textes courts), ménage quotidien. Les projets publiés vivent sur une adresse à part (p.truvector.net) : le code d'un auteur ne tourne jamais là où les gens se connectent à la Forge. Le site et l'API partagent vectra.truvector.net (le Worker y prend /api/*).

Mettre en route :

cd api
npx wrangler d1 create vectra-projets          # recopier l'identifiant dans wrangler.toml
npx wrangler d1 execute vectra-projets --file=schema.sql
npx wrangler r2 bucket create vectra-projets
npx wrangler deploy

En local : npx wrangler d1 execute vectra-projets --local --file=schema.sql puis npx wrangler dev --local.

Le CDN (vectra.truvector.net) reçoit npm run construire : dist/sdk/<version>/ (figé, avec le moteur exact testé) et dist/sdk/<majeure>/ (la dernière). Une page publiée ne change jamais d'elle-même.

8.1 Mettre en production : le dossier web/

npm run web produit web/, un dossier autonome à pousser tel quel dans un dépôt GitHub privé (par exemple truvector/vectra-web). Rien n'y est compilé : tout est déjà construit et contrôlé ici.

Dans web/RôleEn ligne
site/Cloudflare Pages : accueil, /forge, /docs/ (ce manuel), /exemples/, /sdk/<version>/ (figé), /sdk/<majeure>/ (la dernière), le moteur testé, version.jsonhttps://vectra.truvector.net/
api/le Worker vectra-projets, routes de production activesvectra.truvector.net/api/* et p.truvector.net
.github/workflows/deployer.ymlà chaque poussée sur main : déploie site/ puis, si la variable D1_ID est remplie, crée R2, pose les tables (sans rien effacer) et déploie le Worker—
README.mdle pas-à-pas : dépôt, jeton Cloudflare, secrets, domaine, API, mise à jour—

Un seul domaine pour le site et l'API (vectra.…), donc la Forge publie sur /api sans réglage ; les projets publiés restent sur leur adresse séparée (p.…). Ce dépôt n'a pas besoin du jeton du moteur (MOTEUR_LECTURE) : le moteur testé est déjà dans site/. C'est aussi pourquoi il doit rester privé.

Autres adresses : npm run web -- --domaine mondomaine.fr --sous-domaine vectra --publication p --projet-pages vectra-web. Mettre à jour : npm run web, puis recopier web/ dans le dépôt de production (en gardant son .git/) et pousser. npm run test:navigateur contrôle aussi web/site (accueil et ses liens, manuel, exemple, Forge, /sdk/<majeure>/element.js dans une page tierce).


9. GitHub et la mise à jour du moteur

Les deux dépôts sont privés, et rien ne passe par npm (décision TRUvector).

FichierQuandQuoi
.github/workflows/ci.ymlchaque pousséetests unitaires (dont la parité avec la Forge Python et la validité WGSL par naga), puis contrôles navigateur (Chromium sans carte graphique) contre le Worker lancé en local
.github/workflows/moteur-maj.ymlle moteur publiemet à jour vectra.moteurVersion, ouvre une demande de fusion ; mineure ou correctif : fusion automatique si la CI est verte, majeure : relecture
.github/workflows/publier.ymlétiquette vX.Y.Zconstruit dist/, l'attache à la Release, le dépose sur Cloudflare Pages
.github/workflows/deployer.ymlpoussée dans api/déploie le Worker

Secrets : MOTEUR_LECTURE (lecture seule de truvector/vectra), CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID. Le dépôt du moteur doit porter une étiquette par version (v7.9.15) et, quand il publiera (lot 8.8), envoyer un repository_dispatch « moteur-publie » à vectra-sdk.

Ce qui se passe quand le moteur change : un correctif ou un ajout (style, scène, lumière) arrive sans rien toucher ; un nouvel export s'utilise quand on veut ; un changement de contrat (majeure) est refusé par la plage ^8 jusqu'à adaptation. Au démarrage, l'adaptateur vérifie le numéro de contrat du moteur et refuse, avec un message clair, un moteur incompatible.


10. Dans le détail

10.1 Performances

Dans un moteur à champs de distance, chaque forme coûte à chaque pas de chaque rayon. Ce qui aide : répéter (repeter, grille, anneau : une copie coûte comme une forme) plutôt que dupliquer ; regrouper des formes proches sous une union (elle est élaguée par sa sphère englobante) ; éviter les grandes fondre à beaucoup d'enfants. La jauge de la Forge guide ; la qualité automatique et la résolution dynamique du moteur font le reste.

10.2 Sans WebGPU

Le SDK assemble la scène en variante WebGL2 du moteur et la traduit en GLSL ES 3.0 avec naga-wasm 30.2.0 (copie embarquée dans vendor/naga-wasm/ ; à défaut, le CDN jsDelivr, version épinglée). La sortie est identique, à l'octet, à celle de naga-cli avec les mêmes options (--keep-coordinate-space --profile es300) ; la Forge du moteur ajoute --compact, que naga-wasm ne sait pas faire : son GLSL est 5 à 10 % plus court, le nôtre garde quelques fonctions inutilisées, sans effet sur l'image. La traduction se fait dans un Worker (la page ne gèle pas pendant une recompilation), avec repli sur la page si le Worker ne démarre pas. Le moteur 8.3 apportera son propre naga en WebAssembly ; l'adaptateur s'en servira à la place.

10.3 Les erreurs

Toutes les erreurs du SDK sont des ErreurVectra : code, message (dans la langue choisie), fr, en, details. Codes : contrat, bibliotheque, shader, wgsl-libre (avec details.noeud, nom, ligne), glsl, webgl2-graphe, rendu, scene, cible, style, capacite, script, detruit, rv.

10.4 Les tests

npm test                    # contrôles unitaires (graphe, WGSL, assembleur, lexique, contrat, API, actifs, TypeScript, export, GitHub)
npm run construire          # dist/, nécessaire au contrôle du CDN
npm run test:navigateur     # exemples, gabarits, Forge, CDN, web/, API du monde, documentation, nuage, salon à 8 (≈ 45 min en logiciel)

Les contrôles de la chaîne (tests/unites/contrat.test.js) refusent tout import du moteur hors de l'adaptateur, toute méthode publique sans alias anglais ou sans place dans ce manuel, et un cœur du SDK de plus de 150 Ko compressés. tests/navigateur/docs.mjs exécute chaque exemple du terrain de jeu et chaque bloc de code JavaScript / TypeScript de ce manuel.

Les contrôles navigateur utilisent Chromium sans carte graphique (SwiftShader) : lent mais fidèle. CHROMIUM=<chemin> choisit l'exécutable ; CAPTURES=<dossier> garde des images. Le contrôle du nuage demande le Worker en local (VECTRA_API, par défaut http://127.0.0.1:8787) et s'ignore sinon.


11. La feuille de route, lot par lot

Dit franchement. « Fait » veut dire écrit et contrôlé dans la chaîne ; « moteur 8 » : attend un lot du moteur (le contournement provisoire est en place quand il existe) ; « à mesurer » : demande du vrai matériel ou de vraies personnes.

LotFaitReste
0.1 dépôt et chaînedépôt séparé, adaptateur unique (contrôlé), 4 workflows, contrôles de taillenpm au lieu de pnpm, JSDoc au lieu de TypeScript strict, node:test au lieu de Vitest (choix assumés) ; workflows jamais lancés sur GitHub
0.2 paquet et baliseVectra.creer, 9 scènes livrées, styles, modes (dont image fixe), <vectra-monde>, FR + EN contrôlés, CDN, cœur ≈ 75 Ko compressésArches et photo en relief (données du moteur) ; régions du site (moteur.site(), 8.1) — moteur 8
0.3 graphe → WGSL27 types de nœuds (dont morphose, bruit, fractale), élagage, empreinte, débordement, erreur WGSL libre à sa ligne« briques en flux » et « en cubes » (FluxMonde, champ_cubes : moteur 8) ; Maison du jour en graphe à 30 dB, 300 ms sur portable — à mesurer
0.4 le même graphe partoutGLSL par naga dans un Worker, JS identique au GPU à < 1 mm sur 1 000 points, sonde GPU (WGSL libre)variante casque, style par objet — moteur 8.6 ; sonde en WebGL2 — moteur
0.5 entités, actions, camérasentités, actions, tactile, manette, caméras libre, première personne, orbite, suivre, rail, séquence, vol ; vise, touche, entre, clic, minuteries112 entités à 60 i/s (bloc de 132 vec4 en 7.9.15 : ≈ 60 entités animées avant écriture en dur) ; jeu des Organismes refait (scène livrée fermée en 7.9.15) — moteur 8.4 / 8.6
0.6 scripts, comportements, interfaceTypeScript (types retirés), 13 comportements dont porte, interrupteur, collecte, compte à rebours ; trois jeux (collecte, course, énigme) ; règle fautive coupée après 3 erreursvérification des types
0.7 actifsglTF → .vobj dans un Worker, objets embarqués dans le projet, splats (.vspl, .ply, .spz)ciels .venv, photo → objet (file de vectra-api), bibliothèque CC0 — moteur 8.7 ; « à l'octet près » mesuré sur des modèles générés, pas sur l'avocat du Khronos
0.8 la Forgearbre, inspecteur, poignées de déplacement, bibliothèque, piste de temps, CodeMirror et complétion, coût par nœud et conseils, IndexedDB, import d'objets, export .zippoignées de rotation et d'échelle ; .vectra en archive (c'est un JSON qui embarque tout) ; coûts mesurés au banc ; test avec cinq personnes — à mesurer
0.9 publier et partagerWorker, codes PRJ-, adresse séparée, galerie, signalements, CLI, Exporter vers GitHub (dépôt privé, déploiement compris)connexion OAuth et export essayés contre une API GitHub simulée seulement
0.10 ensemblesalons à 8 (contrôlés), entités partagées à 20 Hz avec un propriétaire, classement—
1.0 ouvert aux créateursmanuel, référence générée, terrain de jeu, 7 gabarits, vectra nouveau, dossier de production web/, tableau de compatibilité, la doc qui tourne dans la chaînesite de documentation déployé (le dossier web/ le contient : reste à le pousser) ; « Use this template » (réglage GitHub)

Et toujours : réalité virtuelle depuis le SDK non (monde.rv.entrer() lève une erreur claire ; noyau séparé du moteur, 8.6) ; formes dans une scène livrée non ; un seul objet importé et un seul nuage de splats par scène (liaisons du moteur 7.9.15) ; textures glTF sous Node : couleur de base seule.


12. Compatibilité

vectra-sdkMoteur testéMoteurs acceptésContratRemarques
0.12.07.9.15^7.9.15 || ^8.0.01mode provisoire (sans porte publique) ; bascule seul sur la porte du moteur 8.x
0.11.07.9.15^7.9.15 || ^8.0.01
0.10.07.9.15^7.9.15 || ^8.0.01première version

La même table est publiée avec le site (/version.json, champ compatibilite). Promesse visée pour la 1.0 : rien ne casse dans la 1.x, et toute 1.x marche avec tout moteur 8.x.


13. Glossaire


Référence

Produite par node outils/doc-reference.mjs à partir du code : elle suit l'API exactement.

Les nœuds du graphe

Chaque nœud a un type, des paramètres (p), des enfants (enfants), et peut avoir un placement (en, rot en degrés, echelle), une matiere et un nom. Un paramètre marqué structure recompile le shader quand il change ; les autres se règlent sans recompiler.

Formes

TypeNom (fr / en)EnfantsParamètres
sphereSphère / Sphere0rayon rayon (f ; défaut 1 ; 0.001 à 1000)
boiteBoîte / Box0taille taille (v3 ; défaut [1, 1, 1])
arrondi arrondi (f ; défaut 0 ; 0 à 100)
cylindreCylindre / Cylinder0rayon rayon (f ; défaut 0.5 ; 0.001 à 1000)
hauteur hauteur (f ; défaut 1 ; 0.001 à 1000)
toreTore / Torus0rayon rayon (f ; défaut 1 ; 0.001 à 1000)
epaisseur épaisseur (f ; défaut 0.25 ; 0.001 à 1000)
capsuleCapsule / Capsule0rayon rayon (f ; défaut 0.3 ; 0.001 à 1000)
hauteur hauteur (f ; défaut 1 ; 0 à 1000)
coneCône / Cone0rayon rayon (f ; défaut 0.5 ; 0.001 à 1000)
hauteur hauteur (f ; défaut 1 ; 0.001 à 1000)
octaedreOctaèdre / Octahedron0rayon rayon (f ; défaut 0.6 ; 0.001 à 1000)
planPlan / Plane0hauteur hauteur (f ; défaut 0 ; -10000 à 10000)
terrainTerrain / Terrain0taille taille (f ; défaut 400 ; 1 à 100000)
relief relief (f ; défaut 6 ; 0 à 2000)
echelle échelle des collines (f ; défaut 40 ; 0.1 à 10000)
graine graine (graine ; défaut « A7K2M9QX »)
objetObjet importé / Imported object0fichier fichier .vobj (texte, structure ; défaut « avocat.vobj »)
wgslWGSL libre / Raw WGSL0code code (renvoie la distance) (texte, structure ; défaut « return length(p) - 1.0; »)
borne demi-taille englobante (0 : sans borne) (v3 ; défaut [0, 0, 0])

Opérations

TypeNom (fr / en)EnfantsParamètres
unionUnion / Union1 à 64—
moinsMoins / Subtract2 à 64—
interIntersection / Intersect2 à 64—
fondreFondre / Blend2 à 64k douceur (f ; défaut 0.5 ; 0.001 à 100)
morphoseMorphose / Morph2t passage (0 : première, 1 : seconde) (f ; défaut 0.5 ; 0 à 1)

Modificateurs

TypeNom (fr / en)EnfantsParamètres
bruitBruit / Noise1amplitude amplitude (f ; défaut 0.15 ; 0 à 100)
frequence fréquence (f ; défaut 1.5 ; 0.001 à 100)
graine graine (graine ; défaut « VECTRA »)
fractaleFractale / Fractal1iterations itérations (f, structure ; défaut 3 ; 1 à 8)
echelle échelle (f ; défaut 2 ; 1.05 à 4)
decalage décalage (v3 ; défaut [1, 1, 1])
arrondirArrondir / Round1rayon rayon (f ; défaut 0.1 ; 0 à 100)
creuserCreuser / Shell1epaisseur épaisseur (f ; défaut 0.05 ; 0.001 à 100)
tordreTordre / Twist1force force (radians par mètre) (f ; défaut 0.5 ; -20 à 20)
plierPlier / Bend1force force (f ; défaut 0.2 ; -10 à 10)
ondulerOnduler / Wave1amplitude amplitude (f ; défaut 0.2 ; 0 à 100)
frequence fréquence (f ; défaut 1 ; 0.001 à 100)
miroirMiroir / Mirror1axe axe (choix, structure ; défaut « x »)

Répétitions

TypeNom (fr / en)EnfantsParamètres
grilleGrille / Grid1pas pas (0 : pas de répétition sur l'axe) (v3 ; défaut [4, 0, 4])
nombre nombre de chaque côté (0 : sans fin) (v3 ; défaut [0, 0, 0])
par-casePar case / Per cell1case côté de la case (f ; défaut 6 ; 0.1 à 10000)
densite densité (f ; défaut 1 ; 0 à 1)
hasard hasard de position (f ; défaut 0.8 ; 0 à 1)
varier rotation au hasard (f ; défaut 1 ; 0 à 1)
graine graine (graine ; défaut « VECTRA »)
sur posé sur (nœud de sol) (ref, structure ; défaut —)
anneauAnneau / Ring1nombre nombre (f ; défaut 8 ; 1 à 512)
rayon rayon (f ; défaut 3 ; 0 à 10000)

Constructeurs de l'API chaînée : sdf.sphere(rayon), sdf.boite(taille, arrondi), sdf.cylindre(rayon, hauteur), sdf.tore(rayon, epaisseur), sdf.capsule(rayon, hauteur), sdf.cone(rayon, hauteur), sdf.octaedre(rayon), sdf.plan(hauteur), sdf.terrain({ taille, relief, echelle, graine }), sdf.wgsl(code, borne), sdf.objet(fichier), sdf.union(...formes).

En anglais : sdf.sphere, sdf.box, sdf.cylinder, sdf.torus, sdf.capsule, sdf.cone, sdf.octahedron, sdf.plane, sdf.terrain, sdf.wgsl, sdf.object.

Les matières

mat.<sorte>(options) : teinte (couleur CSS « #rrggbb » ; en anglais tint), rugosite (roughness), metal, emission, texture (vrai/faux : texture photographiée du moteur).

SorteNom (fr / en)TeinteRugositéMétalÉmissionTexture photo
mat (0)Mat / Matte#b9bcc80.700non
pierre (1)Pierre / Stone#8a83780.8500oui
bois (2)Bois / Wood#8b5a2b0.600oui
platre (3)Plâtre / Plaster#e8e2d60.900oui
metal (4)Métal / Metal#c9ccd20.310oui
verre (5)Verre / Glass#dfeff50.0500non
gelee (6)Gelée / Jelly#7fd1a80.400non
lueur (7)Lueur / Glow#F5C5420.503non
tissu (8)Tissu / Fabric#6b4a7a0.9500oui
terre (9)Terre / Soil#6b4f360.9500oui
eau (10)Eau / Water#2a6f860.0500non
objet (11)Couleurs de l'objet / Object colours#ffffff0.600non

Les comportements

ComportementAnglaisSignature
comportements.tournerbehaviours.spin(vitesse rad/s = 1, axe = "y")
comportements.flotterbehaviours.float(amplitude = 0.3, frequence = 1)
comportements.patrouillerbehaviours.patrol(points, vitesse = 2)
comportements.orbiterbehaviours.orbit(centre, rayon = 3, vitesse = 0.5)
comportements.suivrebehaviours.follow(cible = "joueur", vitesse = 2, distance = 2)
comportements.fuirbehaviours.flee(cible = "joueur", vitesse = 3, distance = 5)
comportements.troupeaubehaviours.herd({ centre, rayon = 15, vitesse = 1, peur = 6 })
comportements.pulserbehaviours.pulse(amplitude = 0.1, frequence = 1)
comportements.portebehaviours.door({ deplacement = [0, 3, 0], vitesse = 2, ouverte = false }) → entite.ouvrir() / fermer() / basculer()
comportements.interrupteurbehaviours.switch`({ cible, mode = "basculer""plaque", distance = 1.8, action = "interagir", sur, poids })`
comportements.collectebehaviours.collectible({ compteur = "Score", total, effet = "eclat", son, valeur = 1 }) → événements « collecte », « collecte-finie »
comportements.compteAReboursbehaviours.countdown(secondes, fin, { libelle = "Temps" })
comportements.minuteriebehaviours.timer(secondes, fin)

Les ciels

CielBasHautSoleilBrumeForce du soleil
jour0.78, 0.74, 0.70.25, 0.47, 0.820.6, 0.45, -0.50.00251
heure-bleue0.55, 0.45, 0.60.12, 0.2, 0.420.5, 0.12, -0.60.0041
aube0.95, 0.66, 0.50.35, 0.45, 0.72-0.6, 0.18, -0.50.0031
studio0.42, 0.42, 0.460.2, 0.2, 0.240.5, 0.6, 0.60.00050.45
nuit0.05, 0.06, 0.120.01, 0.015, 0.040.3, 0.5, -0.40.0021

Alias anglais : day, blue-hour, dawn, night.

Les scènes livrées montables par le SDK (moteur 7.9.15)

organismes, cathedrale, abysse, aurore, atelier, genese, maison, plaine, ville. Les Arches (briques en flux) et la photo en relief demandent des données que le SDK ne fournit pas encore.

Les actions et leurs touches par défaut

Touches données par position (KeyboardEvent.code) : KeyW est Z sur un clavier AZERTY.

ActionTouchesManette
avancerKeyW, ArrowUpstick gauche
reculerKeyS, ArrowDownstick gauche
gaucheKeyA, ArrowLeftstick gauche
droiteKeyD, ArrowRightstick gauche
sauterSpaceA (0)
courirShiftLeft, ShiftRightstick gauche (10)
interagirKeyE, EnterX (2)
lanterneKeyLY (3)
monterKeyR, PageUpR1 (5)
descendreKeyF, PageDownL1 (4)
pauseEscape, KeyPStart (9)

Lexique : méthodes françaises → anglaises

Monde

FrançaisAnglais
ajouteradd
repeterrepeat
entiteentity
entitesentities
joueurplayer
quandwhen
chaqueevery
apresafter
onon
offoff
stylesetStyle
heuretimeOfDay
lanternelantern
sonsound
ancreranchor
capturecapture
pausepause
reprendreresume
objetobject
finend
detruiredestroy
exporterexport
distancedistance
sonderprobe
solSousgroundUnder
surLeSolonGround
hasardrandom
viserpick
infosinfo
uiui
cameracamera
actionsactions
reseaunetwork
rvvr
graphegraph
objetsobjects
chargerload
majGraphegraphChanged
executerrun
styleslistStyles
allerVersflyTo
splatssplats

Camera

FrançaisAnglais
librefree
orbiteorbit
suivrefollow
poserset
allerVersflyTo
positionposition
sequencesequence
railrail
joueurfirstPerson

Objet

FrançaisAnglais
enat
tournerrotate
echellescale
reglerset
matierematerial
cacherhide
montrershow
retirerremove
positionposition
rotationrotationAngles
visiblevisible
rayonradius
centrecenter
donneesdata
noeudnode
echelleActuellecurrentScale

Entite

FrançaisAnglais
avecwith
sanswithout
comportementsbehaviours

InterfaceJeu

FrançaisAnglais
compteurcounter
minuterietimer
messagemessage
finend
effacerclear
detruiredestroy

Forme

FrançaisAnglais
enat
tournerrotate
echellescale
matierematerial
nommername
unionunion
moinssubtract
interintersect
fondreblend
arrondirround
creusershell
tordretwist
plierbend
ondulerwave
miroirmirror
grillegrid
parCaseperCell
anneauring
morphosemorph
bruitnoise
fractalefractal

Lexique : options anglaises → françaises

AnglaisFrançais
walkmarche
jumpsaut
speedvitesse
runcourse
gravitygravite
radiusrayon
heighttaille
positionposition
rotationrotation
scaleechelle
shapeforme
materialmatiere
withavec
datadonnees
onGroundsurLeSol
skyciel
qualityqualite
seedgraine
farloin
rendererrendu
scenescene
stylestyle
modemode
cinemacinema
languagelangue
enginemoteur
maxPixelRatiopixelsMax
transitiontransition
intensityintensite
settingsreglages
distancedistance
autoauto
angleangle
durationduree
offsetdecalage
texttexte
replayrejouer
labellibelle
musicmusique
cellcase
densitydensite
jitterhasard
varyvarier
onsur
centercentre
fearpeur
elevationhauteur
rotatestourne
playersjoueurs
rankingclassement
roomsalon
loopboucle
yawlacet
lookregard
leadavance
displacementdeplacement
opacityopacite
openouverte
targetcible
countercompteur
totaltotal
effecteffet
soundson
valuevaleur
weightspoids
actionaction