diff --git a/v2/immersif-frontiere-plan.md b/v2/immersif-frontiere-plan.md new file mode 100644 index 0000000..3390cf5 --- /dev/null +++ b/v2/immersif-frontiere-plan.md @@ -0,0 +1,467 @@ +# Contenu immersif — frontière entre le Studio IA et le canal XR — 📦 V2 + +> **Statut** : document de frontière, écrit le 2026-09-11. Rien de codé. +> +> **Ce document ne remplace ni [studio-plan.md](studio-plan.md) ni [vr-quest-unity-plan.md](vr-quest-unity-plan.md).** +> Il tranche ce que ces deux plans se renvoient mutuellement en dépendance : le Studio met la 3D en +> lot 11 en disant « dépend du chantier VR », et le plan VR décrit les POI sur GLB (§9) en supposant +> un GLB qui arrive de quelque part, sans jamais dire d'où. Ce n'est pas un trou de conception, c'est +> une frontière que personne ne tient. +> +> ⚠️ **Rien ici n'est V1.** La bascule Postgres et le backlog V1 passent avant. Et le gate du Studio +> reste celui du lot 4 : si un conservateur ne produit pas 10 images cohérentes seul, aucune ligne de +> 3D ne s'écrit. + +--- + +## 1. Les trois couches + +La confusion vient de ce que « module 3D » désigne trois choses distinctes, décrites dans deux +documents différents. Les voici séparées. + +### Couche 1 — Ressources immersives + +Trois nouveaux types de ressource, qui s'affichent **partout où une ressource s'affiche** : + +``` +ResourceType += Panorama360 -- image équirectangulaire + Video360 -- vidéo équirectangulaire + Model3D -- GLB +``` + +Indépendant de la VR. Indépendant de l'IA. Un client web sans casque et sans Studio en profite. + +⚠️ **Les trois valeurs se décident d'un coup, en une migration, en fin d'enum.** Aujourd'hui +`ResourceType` s'arrête à `Text = 10` (`Data/Resource.cs:94-110`, avec le commentaire qui interdit +d'insérer au milieu). `studio-plan.md` §5 réserve déjà `Model3D = 11`. + +**Décision — `Video360` est un type distinct, pas un flag `IsEquirectangular` sur `Video`.** L'enum +distingue déjà `Image` / `ImageUrl` : un type séparé est cohérent avec l'existant et rend le filtrage +trivial dans le sélecteur de ressource, qui est le point de passage unique de toute l'app +(`select_resource_modal.dart:24` embarque `ResourcesScreen`). + +### Couche 2 — Consommation + +Trois usages de ces ressources : + +| Usage | Porté par | Canaux | +|---|---|---| +| `SectionModel3D` + POI (titre, description, audio, multilingue) | une section | web, mobile, VR | +| `ImmersiveBackground` — fond d'une visite | `Configuration` | VR, dégradé ailleurs (§4) | +| `ImmersiveBackground` — fond du menu général | `ApplicationInstance` VR | VR | +| Rendu immersif complet | app Quest (Unity) | VR | + +Le back-office XR (`Device.AppType` + onglet flotte, 8-12 j) se tient debout tout seul dans cette +couche : il rend le canal administrable et démontrable avant tout engagement sur Unity. + +### Couche 3 — Studio IA + +**Une source d'approvisionnement pour la couche 1, pas un module parallèle.** C'est la reformulation +qui règle la frontière : le Studio ne possède pas ses assets, il **alimente la médiathèque**, qui +reste la bibliothèque unique (`studio-plan.md` §3.0). + +Ce que le Studio apporte que le client ne peut pas obtenir ailleurs, dans l'ordre de valeur : + +1. **L'asset atterrit dans la médiathèque, référencé par une section, donc il part offline.** + C'est le point non réplicable. Un asset généré ailleurs est un fichier à re-uploader à la main, + et il ne part offline que s'il finit référencé par un champ de section + (`GetReferencedResourceIds`). +2. **L'identité visuelle est injectée côté serveur, jamais retapée.** La cohérence des quarante + images d'un parcours, pas l'accès au modèle. +3. **Le bon format, la bonne dimension, le bon prompt contextuel** — le titre et la description de + la fiche alimentent le prompt, la validation affecte directement au champ cible. + +--- + +## 2. Ce que chaque couche vend + +**Il n'y a pas d'add-on casque.** Arbitré le 2026-08-31 (`vr-quest-unity-plan.md` §6), reconfirmé +ici parce que la question revient : un add-on « VR » ne se vendrait que le jour où l'app Unity existe +*et* où le client achète un casque. L'add-on **« Contenu immersif », 70 €/mois et plus**, vend la +couche 1 — qui s'affiche déjà en web, mobile et kiosk. Un client le souscrit et en a pour son argent +**sans casque**. Le casque est le haut de gamme de l'add-on, pas sa condition d'existence. + +| Ce qui est vendu | Véhicule | Montant | +|---|---|---| +| Ressources immersives (360, GLB, POI) + l'app Quest quand elle sortira | add-on « Contenu immersif » | à partir de 70 €/mois, s'accroche à n'importe quel palier | +| Génération IA (images, puis props et scènes) | add-on Studio, crédits dédiés | voir `studio-plan.md` §3.5 | +| Casques | achat client | hardware | +| Provisioning, config borne, formation, journée sur site | onboarding one-shot | 1200-1500 €, **seulement s'il y a des casques** | +| Modélisation 3D d'atelier, photogrammétrie d'objets de collection | sur devis, hors Studio | 3 à 15 k€ | + +### 2bis. Quota IA et crédits Studio — deux monnaies, et pourquoi + +> **Arrêté le 2026-09-11.** La question « le Studio consomme-t-il le quota IA ? » revient à chaque +> conversation. Réponse : **non**, et voici le critère pour ne plus se la reposer. + +**Le critère n'est pas « IA / pas IA ». C'est « consommé en temps réel par le visiteur » contre +« produit une fois par le client ».** Cette ligne décide du modèle de facturation parce qu'elle décide +de la forme de la courbe d'usage. + +| | **Quota IA** | **Crédits Studio** | +|---|---|---| +| Unité | tokens / mois | crédits | +| Renouvellement | reset mensuel, non reporté | **rechargeables, expiration 12 mois** | +| Véhicule | attribut du palier | add-on | +| Courbe d'usage | récurrente, suit la fréquentation | **en pics** (préparation d'une expo) | +| Couvre | chat visiteur, canal vocal, traduction | images, 3D, scènes, vidéo, **TTS pré-généré** | + +Le reset mensuel est **juste** pour le quota IA : l'usage suit la fréquentation, il est corrélé aux +vacances scolaires, et il est normal qu'il ne se reporte pas. + +Il est **faux** pour le Studio. Un musée génère 200 images en trois semaines pour préparer une expo, +puis rien pendant cinq mois. Un forfait mensuel se gaspille dix mois sur douze : le client a le +sentiment de payer pour rien, et la capacité provisionnée ne sert pas. + +➡️ **Correction à porter dans `studio-plan.md` §3.5** : `ImageCreditsMonthKey` et le +`Kind: MonthlyReset` deviennent inutiles. Le `CreditLedger` est déjà append-only avec un +`Kind: Grant` — il encaisse le modèle rechargeable **sans changement de schéma**. Gratuit à décider +maintenant, migration en prod plus tard. + +**Le TTS pré-généré va côté Studio**, et c'est contre-intuitif puisque c'est « de l'IA ». Trois +raisons : il se facture au caractère et non au token, il produit un fichier (donc du stockage, comme +une image générée), et il se déclenche par pics quand le conservateur rédige. Le mettre sur le quota +tokens visiteur mélangerait deux courbes sans rapport. + +**La traduction est le cas limite, laissé où il est.** Par le critère ci-dessus elle devrait être en +crédits Studio — déclenchée par le client, une fois, produisant un asset durable. Mais elle est déjà +sur le quota tokens, elle est très bon marché, et la déplacer ne rapporterait rien. Décision : **ne +pas y toucher**, mais le savoir plutôt que de le redécouvrir. + +**Vérifié dans le code le 2026-09-11 — ce que le quota IA couvre réellement :** + +| Endpoint | Débite le quota | Note | +|---|---|---| +| `POST /api/Ai/chat` | ✅ `AiController.cs:394` | | +| `POST /api/Ai/translate` | ✅ `AiController.cs:347` | | +| `POST /api/Ai/reindex/{instanceId}` | ❌ | **Les embeddings RAG coûtent et passent gratuitement** | +| `GET /api/Ai/insights/{instanceId}` | ❌ | idem | + +⚠️ **Trou existant, indépendant du Studio** : deux endpoints facturés chez Google ne débitent rien. +Et `AiTokensPerMonth` vaut `0` en Essentiel et Pro, `20_000_000` en Premium +(`MyInfoMateDbContext.cs:607-637`) — donc le quota IA est aujourd'hui un **attribut du palier**, pas +un add-on. À ne pas confondre avec les crédits Studio, qui sont un add-on. + +⚠️ **L'add-on immersif doit porter sa propre enveloppe de stockage, explicite et finie, avec +facturation du dépassement.** Un client Pro à 99 € a un quota dimensionné pour du Pro ; une vidéo 360 +de 5 minutes pèse des Go, un monde exporté en splats aussi. Le principe est posé dans +`vr-quest-unity-plan.md` §6 — **il n'est toujours pas chiffré, et il doit l'être avant d'ouvrir la +couche 1 à la vente.** + +--- + +## 3. Génération de scène 3D — état réel du marché + +> **Révisé le 2026-09-11.** Une première passe de cette analyse concluait « ne pas faire, l'assemblage +> est la seule voie ». C'était fondé sur un état du marché périmé : la conclusion change, la limite +> qui la motivait reste vraie. + +### Ce qui existe + +[Marble](https://www.worldlabs.ai/blog/marble-world-model) (World Labs) génère des environnements 3D +persistants depuis du texte, une image, une vidéo ou un panorama, et **expose une World API depuis +janvier 2026** — à partir de **~0,12 $ le monde en draft**. Exports : splats (SPZ/PLY), panorama 360, +collider mesh basse résolution, et **mesh texturé GLB** sur le palier Pro, qui porte aussi les droits +commerciaux. + +➡️ **Ça entre dans la couche fournisseur sans rien y changer** : un `GenerationModel` de plus en base, +`ProviderKey = "worldlabs"`, `Kind = Scene3D`. C'est exactement ce que `studio-plan.md` §3.6 a été +conçu pour absorber. + +### La limite qui ne bouge pas + +Un monde Marble est une **scène capturée**, excellente vue depuis et autour de son point de vue, +qui dégrade dès qu'on s'en éloigne. Chiffres rapportés côté Quest 3 standalone : plafond raisonnable +autour de **500k splats** (2M restant du domaine du VR filaire), environnements cadrés sur une grille +de ~10×10 m, et un cas mesuré à **12 fps sous Unity contre 19 dans le navigateur Oculus**. Une borne +publique doit tenir 72 fps, huit heures par jour, sans personne à côté. + +Et il n'y a pas de sémantique : pas de sol identifié, pas d'objets nommés, pas d'interactions. Le +collider mesh fourni donne la collision, pas la compréhension de la scène. + +### Position retenue + +| Usage | Verdict | +|---|---| +| Fond immersif d'une visite ou du menu VR (le visiteur regarde autour de lui depuis un point fixe) | ✅ **oui**, c'est le cas d'usage nominal de la techno | +| Monde de référence en **roomscale stationnaire** : le visiteur est *dans* la scène, sur ~3 m | ✅ **oui** — et c'est mieux qu'un assemblage de props Meshy | +| Environnement navigable, le visiteur se déplace librement dedans | ❌ **non** en V2. Ni la perf en borne, ni la sémantique n'y sont | +| Props d'ambiance générés depuis du texte, placés sur ancres | ✅ **oui**, faible risque : un prop raté se jette | + +**Le cadre retenu est le roomscale stationnaire, pas un diorama.** Arbitré le 2026-09-11 : une zone +d'environ 3 m où le visiteur tourne sur lui-même, se penche et tend le bras, mais ne se déplace pas. +C'est exactement la zone où Marble est bon — son point de capture — et où le budget de perf tient. + +Ce n'est pas un pis-aller : la contrainte supprime d'un coup la locomotion, les collisions, le motion +sickness, la cohérence d'échelle au-delà du champ proche et l'essentiel du budget de perf. Et elle est +*meilleure* produit qu'un diorama vu de l'extérieur, parce que le visiteur est **dans** la scène. +Le but n'a jamais été de faire un jeu VR. + +**Placement sur ancres prédéfinies, jamais libre.** Le template d'environnement Unity expose N +snap points ; le client y accroche ses props. Un éditeur de placement libre en 3D dans un back-office +web est un chantier à lui seul, pour un gain nul à ce stade. + +### Image → GLB : mature en API, piégeux en produit + +Meshy / Tripo / Rodin ont des API qui marchent, et le multi-vues améliore nettement le résultat. Mais +ce qui sort est un objet isolé, silhouette lisible, topologie sale, texture baked, échelle et pivot +arbitraires. + +⚠️ **Pour un objet de collection, le problème n'est pas la qualité, c'est la véracité.** Un visiteur +qui fait tourner « le casque de Léon » croit voir l'objet. Deux conséquences : + +- Sur un `Model3D` généré, la provenance visible est **obligatoire, non désactivable** — contrairement + aux images, où le badge suffit et la gravure est une option (`studio-plan.md` décision n°6). +- L'offre doit séparer explicitement **« objet numérisé »** (photogrammétrie, sur devis, hors Studio) + de **« illustration 3D »** (Studio). Les vendre sous le même mot est un risque réputationnel chez + une institution scientifique. + +### Le vrai sur-scope : l'éditeur, pas la génération + +`vr-quest-unity-plan.md` §9 signale l'éditeur de placement de POI comme « le seul morceau non +trivial ». Il le sous-estime : **Flutter web n'a pas de stack 3D éditable.** `model_viewer_plus` est +une WebView autour de ``, en lecture seule. + +➡️ **Décision à prendre maintenant, parce qu'elle conditionne tout le chiffrage** : l'éditeur de POI +3D est une **page three.js embarquée en iframe**, pas un widget Flutter. Précédent dans le repo : la +démo villa d'Echternach (`visitapp-web/src/app/demo/villa-echternach/scene.ts`). + +--- + +## 4. `ImmersiveBackground` — un type, deux porteurs + +`Configuration` porte aujourd'hui `ImageId` et `LoaderImageId`, rien d'immersif. Poser un +`Configuration.BackgroundResourceId` à plat, puis un autre champ sur l'`ApplicationInstance`, puis un +troisième pour la scène, donne quatre champs « background » incohérents dans six mois. + +``` +ImmersiveBackground + ResourceId + Kind -- Pano | Video360 | Scene3D + FallbackResourceId? -- 2D, pour les canaux qui ne savent pas rendre +``` + +Porté par **`Configuration`** (le fond d'une visite) **et** par l'**`ApplicationInstance` VR** (le +fond du menu général — l'équivalent de home v3 côté mobile). Un seul type, deux porteurs, pas deux +modèles. + +**Dégradation par canal, écrite dans le contrat** — sinon les fonds casseront silencieusement : + +| Canal | `Pano` | `Video360` | `Scene3D` | +|---|---|---|---| +| VR (Quest) | rendu | rendu | rendu | +| Web | visite pivotante | visite pivotante | fallback 2D | +| Mobile | fallback 2D | fallback 2D | fallback 2D | +| Kiosk | fallback 2D | fallback 2D | fallback 2D | + +Chaque canal déclare ce qu'il sait rendre ; le fallback est `Configuration.ImageId` par défaut. + +--- + +## 5. Décisions de rétro-compatibilité, du plus cher au moins cher + +Les points **1 à 5 doivent être écrits dans `studio-plan.md` avant sa première migration.** Les +suivants peuvent s'écrire au fil de l'eau. + +### 1. Pipeline d'ingestion unique côté serveur + +**Le trou le plus cher, et il est structurel, pas accidentel.** Aujourd'hui **l'upload est fait par +le navigateur en direct** (`resources_screen.dart:247-256`) ; le backend ne sait que supprimer +(`IResourceBlobService.DeleteAsync`). Le Studio, lui, produit côté serveur et post-traite **à la +validation d'un brouillon**. + +➡️ Un GLB **uploadé** ne traverserait aucun traitement, un GLB **généré** traverserait le chemin +Studio. Deux chemins par construction. + +- [ ] Déplacer le point de passage de « validation d'un asset Studio » à **« ingestion de tout asset, + quelle que soit sa provenance »** +- [ ] Un seul service de post-traitement, appelé par les deux chemins +- [ ] Le lot 0 (`UploadAsync`, `CopyAsync`, `ProbeSizeAsync`) est le prérequis mais **ne suffit pas** + +Coût du retrofit : l'upload navigateur direct est partout dans manager-app, et il faudrait retraiter +la base d'assets existante. + +### 2. Normalisation canonique des GLB + +- [ ] Pivot recentré, échelle normalisée, up-axis fixé — **déterministes** +- [ ] Budgets de validation (tris, taille de texture, poids) : **rejet**, pas réparation +- [ ] Draco + KTX2 dans le même passage +- [ ] `Model3DVersion` sur la ressource + +Coût du retrofit : **tout POI posé avant est perdu.** C'est ce qui rend ce point non négociable, pas +l'élégance du pipeline. + +### 3. Invalidation et repositionnement des POI + +Absent des deux plans. Et c'est structurellement plus grave qu'en géo : une lat/lon survit à un +changement de fond de carte, un (x,y,z) local est **détruit** par un changement de pivot, d'échelle ou +d'up-axis. + +- [ ] POI attachés à `(resourceId, modelVersion)` +- [ ] Au remplacement ou à la régénération : POI **conservés et marqués « à revalider »**, jamais + supprimés en silence +- [ ] Écran de repositionnement dans l'éditeur + +Peu de code si pris tôt. Coûteux en confiance client si découvert en prod. + +### 4. Lignage en colonnes typées + +`Resource.AiProvenance` (jsonb) distingue uploadé (null) de généré (non-null) et liste des +`referenceResourceIds[]`. **Mais un tableau dans un jsonb n'est pas un lignage requêtable**, et rien +ne couvre un GLB **dérivé** — reprocessé, décimé, regénéré depuis le même jeu de photos. + +- [ ] `Resource.Origin` : `Uploaded | Generated | Derived` — **colonne**, pas jsonb +- [ ] `Resource.SourceResourceId` nullable — le lignage parent/enfant +- [ ] `AiProvenance` reste pour le détail (prompt effectif, seed, version d'identité) + +Coût du retrofit : migration de données sur des assets déjà en base. + +### 5. Renommer la monnaie de crédits — ✅ **porté dans `studio-plan.md` le 2026-09-11** + +`ImageCreditsPerMonth`, `ImageCreditsThisMonth`, `ImageCreditsMonthKey`, `ImageCreditsHardCap`, +`SubscriptionPlan.ImageCreditsPerMonth`. Or une 3D coûte 10 à 50 fois une image, et un monde Marble +n'est pas une image du tout. + +- [x] `ImageCredits*` → **`StudioCredits*`** partout +- [x] `GenerationModel.CreditCost` déjà par modèle : rien d'autre à faire côté tarification +- [x] **Et le modèle change aussi** : crédits **rechargeables à expiration 12 mois** au lieu d'un + quota mensuel. Un musée génère en pics, pas linéairement — un forfait mensuel se gaspille dix + mois sur douze. Voir `studio-plan.md` §3.5 et le §2bis ci-dessus + +**Gratuit aujourd'hui. Migration de colonnes en prod plus tard.** Le retrofit le moins cher à éviter +et le plus bête à subir. ➡️ **Fait** : décision n°3 du tableau §2, bloc §3.5, schéma §5 et lot 1 de +`studio-plan.md` sont à jour. Reste ouvert : le mapping Stripe `checkout.session.completed` → +`Kind: Grant` pour la recharge. + +### 6. Contrat fournisseur : multi-fichier et par kind + +`ProviderResult` est pensé mono-fichier image. Une génération 3D retourne GLB + preview + parfois +textures séparées ; un monde retourne splat + collider mesh + panorama. + +- [ ] `ProviderResult` multi-fichier +- [ ] `GenerationKind` += `Model3D`, `SceneProp`, `Scene3D` +- [ ] **Timeout et politique de retry par kind** — le `PollFallback` à +90 s et le polling front à 2 s + de `studio-plan.md` §3.7 sont dimensionnés pour un job image de 20-60 s +- [ ] **Job composite** : générer 8 props est un job parent + N enfants, pas un job + +### 7. `ImmersiveBackground` comme type réutilisable + +Voir §4. Deux porteurs, un type, dégradation par canal dans le contrat. + +### 8. Les trois valeurs `ResourceType` d'un coup + +Voir §1, couche 1. + +### 9. Éditeur 3D = iframe three.js + +Décision d'architecture, pas d'implémentation. Voir §3. + +### 10. Chiffrer l'enveloppe de stockage de l'add-on immersif + +Voir §2. Posé en principe le 31/08, jamais chiffré. + +### 11. Provenance obligatoire sur un `Model3D` généré + +Voir §3. Et séparer « objet numérisé » de « illustration 3D » dans l'offre. + +--- + +## 6. Droits et modération — ce qui reste ouvert + +Couvert par `studio-plan.md` : `rightsHolder` = l'instance (les droits sont au client, écrit dans la +donnée), provenance complète, badge visiteur traduit dans les langues de la configuration, et la +redirection des personnes identifiables vers les personnages. + +**Pas couvert, et spécifique à la 3D** : rien sur les objets dont le client **n'a pas** les droits — +prêts, dépôts, collections tierces. C'est le quotidien d'un musée, et générer un GLB depuis la photo +d'un objet en dépôt n'est pas anodin. + +➡️ Traitement retenu : **CGU + une mention explicite dans l'écran de génération 3D**, pas de +vérification automatique. Pas de modération automatique entrée/sortie non plus. Acceptable au stade +pilote — **à condition de l'assumer par écrit**, pas de le découvrir. + +--- + +## 7. Chemin critique + +Ce n'est pas celui qu'on croit. La génération 3D est en toute fin, et c'est la brique la **moins** +risquée de la liste, parce qu'à ce stade tout ce qui la consomme existe et est validé. + +``` +Lot 0 — socle de stockage serveur (prérequis dur, sert aussi le TTS) + └─ + pipeline d'ingestion unique (décision n°1) + └─ Ressource 360° (le plus petit des trois chantiers médias) + └─ SectionModel3D + POI (GeoPoint réutilisé à 80 %) + └─ Back-office XR (8-12 j, se tient debout tout seul) + └─ Studio images (lots 1 à 6) + └─ ⛔ GATE lot 4 : un conservateur, 10 images, seul + └─ Studio 3D + scènes + └─ App Unity (go/no-go client pilote) +``` + +Le go/no-go de l'app Unity reste conditionné à **un client pilote disposant déjà de contenu immersif** +— et la couche 1 rend cette condition atteignable, puisqu'elle permet au client de *fabriquer* ce +contenu au lieu de devoir le posséder d'avance. C'est le changement le plus important qu'apporte le +Studio au dossier XR. + +--- + +## 8. Ce qu'on ne fait pas + +- **Environnement VR navigable** en V2 — roomscale stationnaire ~3 m seulement (§3) +- **Placement libre de props** — ancres prédéfinies +- **Locomotion en VR** — le visiteur tourne sur lui-même, il ne marche pas +- **Les mécaniques de jeu du §9** — notées, pas planifiées +- **Photogrammétrie / fac-similé d'objets de collection** — sur devis, hors Studio +- **Modération automatique** entrée/sortie — CGU + provenance + `rightsHolder` (§6) +- **Retopologie ou upscaling automatique** — budgets de validation et rejet, pas de réparation +- **Refonte du routing de manager-app** — les deux `switch` numérotés en dur de `main_screen.dart` + sont acceptés tels quels, comme dans `studio-plan.md` + +--- + +## 9. Mécaniques de jeu immersives — 📝 idées notées, rien de planifié + +> **Noté le 2026-09-11, à la suite du canal VR — pas dans son périmètre.** Aucune de ces mécaniques +> n'est chiffrée ni engagée. Elles sont ici pour ne pas être reperdues, et parce que **deux d'entre +> elles réutilisent des données qui existent déjà**, ce qui change leur coût relatif. + +**Le principe structurant, à ne pas perdre** : un jeu configurable par le client **n'est pas un +moteur de jeu, c'est un type de section**. Trois mécaniques génériques couvrent l'essentiel de ce qui +a du sens en musée, et toutes tiennent dans les ~3 m du roomscale stationnaire. Le client compose ; +un vrai jeu sur mesure reste un devis. + +| Mécanique | Ce que c'est | Données | Coût | +|---|---|---|---| +| **Répondre** | Quiz immersif : question devant, réponses sur 3-4 panneaux autour, réponse au regard ou à la main. Fond = `ImmersiveBackground` de la configuration | **`SectionQuiz`, déjà là** | rendu seul, **config nulle** | +| **Trouver** | Chasse aux détails dans un pano 360 : « trouve les 5 détails », « repère les 3 erreurs ». Le client pose des hotspots | même geste que poser des POI → **le même éditeur iframe sert deux fois** | faible, et **ça marche aussi en web** sur pano pivotant → se vend sans casque | +| **Ordonner** | Tri chronologique ou logique : attraper des objets autour de soi et les ranger. Frise, étapes d'un procédé | **`OrderedTranslationAndResource`, déjà dans le modèle** | faible, config = une liste ordonnée | +| **Avant / après** | « À quoi ressemblait cette place en 1900 ? » — le visiteur devine, puis bascule | **`BeforeAfterPairs`** du lot 9 du Studio | rendu seul | + +**Le meilleur rapport valeur/effort est « Répondre »** : `SectionQuiz` est déjà configuré par les +clients aujourd'hui. Le jeu, c'est le rendu — zéro donnée nouvelle, zéro apprentissage côté client. + +**Le plus intéressant commercialement est « Trouver »**, pour une raison qui n'a rien à voir avec la +VR : il fonctionne sur un pano 2D pivotant en web. Une mécanique de jeu qui se vend **sans casque**, +sur l'add-on immersif seul. + +**Écarté, et pourquoi :** + +| Idée | Pourquoi non | +|---|---| +| Remontage d'objet (amphore à reconstituer) | Effet musée énorme, mais il faut un GLB **pré-découpé en pièces**. Le client ne peut pas le produire seul → casse la règle d'autonomie. **Sur devis uniquement** | +| Escape game VR | La logique conditionnelle n'est pas configurable en trois champs. L'escape game reste sur le canal mobile, où il fonctionne déjà | +| Tir, action, visée | Hors sujet produit | +| Dialogue avec un PNJ scripté | Le guide IA le fait déjà mieux, et sans script à maintenir | +| Tout ce qui demande de marcher | Contredit le roomscale stationnaire (§3) | + +--- + +## 10. Ce que ce document change dans les deux autres plans + +| Plan | À reprendre | +|---|---| +| `studio-plan.md` | Le **lot 11 (3D)** passe de quatre lignes à un lot réel : `Scene3D` comme `GenerationKind`, World Labs dans le catalogue, provenance obligatoire sur `Model3D`. Les décisions n°1, 4, 5 et 6 du §5 ci-dessus touchent des lots **antérieurs** (lot 0, lot 1, lot 3) et doivent y être écrites. | +| `vr-quest-unity-plan.md` | Le **§9 (POI sur GLB)** gagne la version de modèle, l'invalidation des POI et la décision « éditeur en iframe three.js ». Le **§6 (pricing)** gagne l'enveloppe de stockage chiffrée. Le §2 lot V-4 gagne `ImmersiveBackground` comme source du fond de scène. | +| `todo-features.md` | La « Ressource 360° » devient les **trois** valeurs d'enum, en une migration. | +| Kanban | Les cartes 250, 280, 300, 320 et 330 restent valides. Ce document est la source à citer en `src:` pour la frontière. | diff --git a/v2/studio-plan.md b/v2/studio-plan.md index b5e4061..0bdbb4a 100644 --- a/v2/studio-plan.md +++ b/v2/studio-plan.md @@ -116,7 +116,7 @@ Tout sur `Instance`, dupliqué depuis `SubscriptionPlan` : `StorageQuotaBytes`, |---|---|---| | 1 | Portée de l'identité visuelle | **Une par instance** (le musée) + **surcharge optionnelle par configuration** (parcours thématique, ex. Halloween). Cohérent avec `Configuration.PrimaryColor/SecondaryColor/Languages` qui existent déjà | | 2 | Souveraineté | **Champ de configuration déclaratif** pour l'instant. La couche fournisseur rend le changement possible sans réécriture ; pas de chantier stockage EU aujourd'hui | -| 3 | Crédits | **Monnaie dédiée**, distincte des tokens IA. Griller son quota d'images ne doit pas priver les visiteurs du guide IA | +| 3 | Crédits | **Monnaie dédiée**, distincte des tokens IA. Griller son quota d'images ne doit pas priver les visiteurs du guide IA. **Révisé le 2026-09-11 : crédits rechargeables à expiration 12 mois, pas de quota mensuel** — voir §3.5 | | 4 | Rôle valideur | **Permission détachée** (`Manager.assetvalidation`), pas un 5e rôle. Accordée d'office à `InstanceAdmin`, activable sur un `ContentEditor` | | 5 | Brouillons | **Crédits débités toujours** (l'appel a coûté). **Quota stockage compté seulement à la validation.** Variantes non retenues supprimées à la fermeture du panneau ; balayage Hangfire à 30 j comme filet | | 6 | Mention IA | **Pas de gravure dans les pixels par défaut** — irréversible, et laid sur une illustration de musée. Provenance en base + badge UI visiteur dans les langues du projet + métadonnées fichier. Gravure disponible en **option par instance** pour l'institution qui l'exigerait par écrit | @@ -534,28 +534,57 @@ onglet fermé, navigateur planté. Précédents en place : `AuditLogPurgeService ### 3.5 Crédits et garde-fous ``` -SubscriptionPlan += HasStudio, ImageCreditsPerMonth -Instance += StudioEnabled, ImageCreditsPerMonth, ImageCreditsThisMonth, - ImageCreditsMonthKey, ImageCreditsHardCap, - StudioPerUserDailyCap, StudioProviderRegion +SubscriptionPlan += HasStudio, StudioCreditsGranted -- dotation à la souscription +Instance += StudioEnabled, StudioCreditsBalance, StudioCreditsExpireAt, + StudioCreditsHardCap, StudioPerUserDailyCap, StudioProviderRegion CreditLedger (append-only, exportable CSV) Id, InstanceId, UserId, GenerationJobId?, Kind, Amount, BalanceAfter, ModelKey, CreatedAt, Note - Kind: Reserve | Charge | Refund | Grant | MonthlyReset + Kind: Reserve | Charge | Refund | Grant | Expire ``` +> **Révisé le 2026-09-11 — deux changements, tous deux gratuits maintenant et coûteux après la +> première migration.** +> +> **a) `ImageCredits*` → `StudioCredits*`.** Une 3D coûte 10 à 50 fois une image, et un monde généré +> n'est pas une image du tout. Le nom `ImageCredits` aurait menti dès le lot 11. `GenerationModel.CreditCost` +> porte déjà le coût par modèle : rien d'autre à changer côté tarification. +> +> **b) Crédits rechargeables à expiration 12 mois, au lieu d'un quota mensuel.** Le reset mensuel +> suppose un usage linéaire. **Un musée ne travaille pas comme ça** : il génère 200 images en trois +> semaines pour préparer une expo, puis rien pendant cinq mois. Un forfait mensuel se gaspille dix mois +> sur douze — le client a le sentiment de payer pour rien, et la capacité provisionnée ne sert pas. +> Le modèle rechargeable est aligné sur l'usage réel *et* sur un coût qui est à l'acte. +> +> ➡️ Disparaissent : `ImageCreditsPerMonth`, `ImageCreditsThisMonth`, `ImageCreditsMonthKey`, et le +> `Kind: MonthlyReset`. Apparaissent : `StudioCreditsBalance`, `StudioCreditsExpireAt`, et un +> `Kind: Expire` pour tracer la péremption dans le journal. **Le `CreditLedger` encaisse le modèle +> sans changement de structure** — il était déjà append-only avec un `Kind: Grant`. +> +> ⚠️ **Reste ouvert** : la recharge côté Stripe. Le webhook ne gère que `checkout.session.completed` +> et `invoice.payment_failed` (`StripeWebhookController.cs:62-65`), rien sur les lignes d'abonnement. +> Un achat de crédits est un paiement ponctuel, donc `checkout.session.completed` suffit — mais le +> mapping session → `Kind: Grant` est à écrire. Pour les premiers clients, un `Grant` posé à la main +> depuis l'écran SuperAdmin suffit. +> +> **Le critère qui sépare les deux monnaies**, pour ne plus se reposer la question : *consommé en +> temps réel par le visiteur* (chat, vocal → quota IA, reset mensuel, attribut du palier) contre +> *produit une fois par le client* (images, 3D, scènes, vidéo, **TTS pré-généré** → crédits Studio, +> rechargeables, add-on). Détail et vérification dans le code : +> [immersif-frontiere-plan.md](immersif-frontiere-plan.md) §2bis. + **Le plafond dur n'est pas le bon outil contre le stagiaire.** Le scénario « vider un budget en une après-midi » est un problème **par utilisateur**, pas par organisation : le budget du mois est justement là pour être dépensé sur le mois. Deux garde-fous distincts : -- `ImageCreditsHardCap` — plafond organisation que même un dépassement facturé ne franchit pas. +- `StudioCreditsHardCap` — plafond organisation que même un dépassement facturé ne franchit pas. - `StudioPerUserDailyCap` — le vrai rempart : N générations par utilisateur et par jour. **Réservation en deux temps.** `CheckQuota` avant / incrément après suffit à un appel LLM bloquant de 2 s. Il ne suffit pas à dix jobs Hangfire de 40 s lancés en parallèle : les dix contrôles passent avant le premier débit. Donc : `Reserve` à l'enqueue, `Charge` à la complétion (ajusté au coût réel), -`Refund` à l'échec. Solde disponible = `ImageCreditsPerMonth − (charges + réservations ouvertes)`. +`Refund` à l'échec. Solde disponible = `StudioCreditsBalance − réservations ouvertes`. Le **coût estimé** vient de `GenerationModel.CreditCost × VariantCount`, affiché avant le lancement. Le **journal exportable** est une projection CSV de `CreditLedger` — c'est le document que les @@ -1154,10 +1183,10 @@ Une migration, additive, aucune donnée existante touchée — sauf `GuidedStep` ``` Resource += AiProvenance jsonb? -Instance += StudioEnabled, ImageCreditsPerMonth, ImageCreditsThisMonth, - ImageCreditsMonthKey, ImageCreditsHardCap, - StudioPerUserDailyCap, StudioProviderRegion, IsAiWatermarkBurned -SubscriptionPlan += HasStudio, ImageCreditsPerMonth +Instance += StudioEnabled, StudioCreditsBalance, StudioCreditsExpireAt, + StudioCreditsHardCap, StudioPerUserDailyCap, StudioProviderRegion, + IsAiWatermarkBurned +SubscriptionPlan += HasStudio, StudioCreditsGranted User += CanValidateAssets VisitorQuestion += PersonaId? -- sans lui, les stats restent agrégées sur un -- assistant imaginaire : impossible de voir qu'un @@ -1219,7 +1248,8 @@ compression serveur 2560/q82, ajout de `LB`. ### Lot 1 — Crédits `CreditLedger`, colonnes `Instance`/`SubscriptionPlan`, réserve/charge/remboursement, `GET /credits`, -export CSV. Troisième jauge dans le pied de menu + popover au clic ; détail et journal dans +export CSV. **Crédits rechargeables à expiration, pas de reset mensuel** (§3.5) : le balayage de +péremption est un job Hangfire, précédents en place (`AuditLogPurgeService`). Troisième jauge dans le pied de menu + popover au clic ; détail et journal dans **Abonnement**. Corriger au passage le gating de l'entrée Abonnement (`main_screen.dart:547-550`), aujourd'hui réservée à `plan-essentiel`. Testable seul, sans aucun appel fal.ai. @@ -1291,6 +1321,15 @@ animé, sous une forme qui tient en 2026. Meshy 6 / Tripo, objets isolés, export GLB, `ResourceType.Model3D`. Suppose un viewer GLB côté visiteur — dépend du chantier VR/XR (`vr-quest-unity-plan.md`). +⚠️ **Ce lot faisait quatre lignes et se contentait de renvoyer au plan VR, qui lui-même renvoyait +ici.** La frontière est tranchée depuis le 2026-09-11 dans +[immersif-frontiere-plan.md](immersif-frontiere-plan.md), qui ajoute : `Scene3D` comme +`GenerationKind` (World Labs / Marble, API depuis janvier 2026, ~0,12 $ le monde draft), +provenance **obligatoire et non désactivable** sur un `Model3D` généré, `ProviderResult` +multi-fichier, et surtout **cinq décisions qui touchent des lots antérieurs à celui-ci** — dont le +pipeline d'ingestion unique (lot 0) et le renommage des crédits (lot 1). Les lire avant d'écrire +la première migration du Studio, pas en arrivant au lot 11. + **Le test de réussite se joue à la fin du lot 4.** Si à ce stade un conservateur ne produit pas 10 images cohérentes seul, les lots suivants n'y changeront rien.