372 lines
23 KiB
Markdown
372 lines
23 KiB
Markdown
# VR Meta Quest — app Unity + onglet XR dans manager-app — 📦 V2
|
||
|
||
> Relevé le 2026-08-31. Toutes les affirmations « existe / n'existe pas » ci-dessous ont été
|
||
> vérifiées **dans le code**, pas dans la doc. Ce fichier remplace la section VR de
|
||
> [../roadmap.md](../roadmap.md) comme référence technique — la roadmap garde le volet commercial.
|
||
>
|
||
> ⚠️ **Rien de ce document n'est V1.** La bascule Postgres et le backlog V1 passent avant.
|
||
|
||
---
|
||
|
||
## 0. Correction d'une affirmation de STATUS.md
|
||
|
||
STATUS.md §5 disait jusqu'ici : VR Meta Quest = « idée, **zéro ligne de code** dans tous les repos ».
|
||
C'est faux depuis au moins juillet 2026. **Le canal VR est déjà un citoyen de première classe du
|
||
modèle de données et de la navigation du back-office.** Ce qui manque, c'est l'app et l'écran
|
||
de flotte — pas les fondations.
|
||
|
||
---
|
||
|
||
## 1. Ce qui existe déjà
|
||
|
||
### 1.1 Backend (`manager-service`)
|
||
|
||
| Brique | Emplacement | État |
|
||
|---|---|---|
|
||
| `AppType.VR` | `Data/ApplicationInstance.cs` — `enum AppType { Mobile, Tablet, Web, VR, Voice }` | ✅ Valeur 3, persistée en base |
|
||
| `Instance.IsVR` | `Data/Instance.cs:35`, `DTOs/InstanceDTO.cs:16`, `InstanceController.cs:212` | ✅ Activable par instance, exposé au DTO, modifiable par l'API |
|
||
| Migration | `20250709151627_AddedApplicationInstanceAndMisc` | ✅ Colonne `IsVR` en base depuis juillet 2025 |
|
||
| `ApplicationInstance` VR | `Controllers/ApplicationInstanceController.cs` — CRUD complet + `application-link` | ✅ Une instance VR se crée déjà par l'API, sans une ligne de code neuve |
|
||
| `AppConfigurationLink` | `Data/AppConfigurationLink.cs` | ✅ Porte déjà `DeviceId` (nullable) — c'est exactement le mécanisme « une config par appareil » du kiosk |
|
||
| Entité `Device` | `Data/Device.cs` | ✅ `Identifier`, `Name`, `Connected`, `BatteryLevel`, `ConnectionLevel`, `AppVersion`, `LastSeen`, `InstanceId`, `ConfigurationId` |
|
||
| `DeviceController` | 7 endpoints (list, detail, create, update, mainInfos, delete) | ⚠️ Existe, mais **hardcodé sur `AppType.Tablet`** (`DeviceController.cs:155`) |
|
||
| Push MQTT « config changée » | `DeviceController.cs:303` → topic `player/{device.Id}` | ✅ Le mécanisme de rafraîchissement à distance est déjà là |
|
||
| Télémétrie | `Data/VisitEvent.cs:27` — `public AppType AppType` | ✅ Un event VR se stocke sans aucun changement de schéma |
|
||
| Export de contenu | `GET /api/configuration/{id}/export` (`ConfigurationController.cs:388`) | ✅ Configuration + sections + ressources en **un seul appel** — le contrat idéal pour un renderer tiers |
|
||
| Auth app cliente | `GET /api/instance/appKeyByPin` (`InstanceController.cs:347`) | ⚠️ Existe, mais `ApiKeyAppType` ne connaît que `{ VisitApp, TabletApp, Other }` |
|
||
|
||
### 1.2 manager-app
|
||
|
||
| Brique | Emplacement | État |
|
||
|---|---|---|
|
||
| `AppType.VR` côté client | `manager_api_new/lib/model/app_type.dart` + `app_type_extensions.dart` | ✅ Valeur 3 présente **et** nommée « VR » |
|
||
| Sous-menu « VR » | `lib/Screens/Main/main_screen.dart:65` — `if (instance.isVR == true)` | ✅ L'entrée de menu apparaît déjà si l'instance a le canal activé |
|
||
| Écran VR | `main_screen.dart:698-705` — `case 'vr'` | ❌ **`Text("TODO vr")`** — c'est littéralement le seul trou de la navigation |
|
||
| Écran de flotte à cloner | `lib/Screens/Kiosk_devices/` — 4 fichiers, 769 lignes | ✅ `kiosk_screen.dart` (grille d'appareils), `device_element.dart`, `dropDown_configuration.dart` (assignation de config), `change_device_info_modal.dart` |
|
||
| Filtre stats par canal | `lib/Screens/Statistics/statistics_screen.dart:81, 177` | ✅ Générique sur `AppType.values`, et `statsChannelVR` **existe déjà en i18n** — le canal VR apparaîtra tout seul dès le premier `VisitEvent` |
|
||
|
||
### 1.3 Ce qui n'existe nulle part
|
||
|
||
- Aucun projet Unity, aucun `.unity`, aucun package Meta XR dans les repos.
|
||
- Aucun rendu 360° : pas de `ResourceType.Panorama360` (planifié en V2, voir
|
||
[../todo-features.md](../todo-features.md) § Ressource 360°), pas de flag « vidéo equirectangulaire ».
|
||
- Aucun `Device` non-tablette : la table est peuplée exclusivement par `tablet-app`.
|
||
|
||
---
|
||
|
||
## 2. Ce qu'il reste à faire
|
||
|
||
### Lot V-1 — Le `Device` devient multi-canal (backend) — **2-3 j**
|
||
|
||
Aujourd'hui un appareil *est* une tablette : `DeviceController.Create` va chercher
|
||
`ApplicationInstances.FirstOrDefault(ai => ai.AppType == AppType.Tablet)` en dur. Un casque
|
||
enregistré par ce chemin serait rattaché à l'instance kiosk et apparaîtrait dans l'onglet Kiosk.
|
||
|
||
- [ ] `Device.AppType` (colonne `int`, **défaut `Tablet` = 1** pour que les lignes existantes ne bougent pas) + migration EF
|
||
- [ ] `DeviceDTO.appType` / `DeviceDetailDTO`, mappé dans `ToDTO` / `ToDetailDTO` / `FromDTO`
|
||
- [ ] `DeviceController.Create` : résoudre l'`ApplicationInstance` sur `newDevice.appType` au lieu de `AppType.Tablet`
|
||
- [ ] `DeviceController.GetAll` : paramètre de requête `appType` optionnel (sans lui, comportement actuel)
|
||
- [ ] `ApiKeyAppType` : ajouter `VrApp` **à la fin** de l'enum (`{ VisitApp, TabletApp, Other, VrApp }`) — l'enum est persisté en int, ne jamais insérer au milieu
|
||
- [ ] `DeviceControllerTests` : un cas « création d'un device VR » qui vérifie le rattachement à la bonne `ApplicationInstance`
|
||
|
||
⚠️ Le champ `Device.ConfigurationId` est marqué `[Required]` et le commentaire du code le dit
|
||
« OLD WAY → AppConfigurationLink ». Le casque doit passer par `AppConfigurationLink`, pas par ce
|
||
champ ; on le remplit quand même pour ne pas casser la contrainte.
|
||
|
||
### Lot V-2 — Onglet XR dans manager-app — **4-5 j**
|
||
|
||
C'est le lot le moins cher : l'écran kiosk est un patron directement réutilisable, la seule
|
||
différence de fond est l'`AppType` filtré.
|
||
|
||
- [ ] `lib/Screens/Vr_devices/vr_screen.dart` — clone de `kiosk_screen.dart` : grille de casques, pincode d'appairage en tête, état connecté/déconnecté
|
||
- [ ] Réutiliser tel quel `device_element.dart`, `dropDown_configuration.dart` (assignation d'une configuration par casque), `change_device_info_modal.dart`
|
||
- [ ] Brancher `main_screen.dart:704` : remplacer `Text("TODO vr")` par `VrScreen(applicationInstanceDTO: applicationInstanceVR)`
|
||
- [ ] Carte casque : ajouter niveau de batterie + `AppVersion` + `LastSeen` (colonnes déjà en base, jamais affichées pour le kiosk)
|
||
- [ ] `manager_api_new` : ajouter `appType` à `device_dto.dart` / `device_detail_dto.dart` et `VrApp` à `api_key_app_type.dart` — **à la main**, la génération OpenAPI ne se relance pas sur ce repo
|
||
- [ ] i18n FR/EN/NL/DE : titre d'onglet, « casques », « aucun casque appairé », libellés batterie/version
|
||
|
||
Rien à faire pour le sous-menu ni pour les stats : les deux sont déjà branchés sur `isVR` / `AppType.VR`.
|
||
|
||
### Lot V-3 — Contrat de contenu pour un client non-Flutter — **1-2 j**
|
||
|
||
Unity ne peut pas consommer le client Dart généré. Deux options :
|
||
|
||
| Option | Coût | Verdict |
|
||
|---|---|---|
|
||
| Unity appelle les endpoints REST un par un, DTO C# écrits à la main | Élevé et récurrent — chaque évolution de section se répercute | ❌ |
|
||
| Unity appelle `GET /api/configuration/{id}/export` (un appel, tout le contenu) et désérialise `ExportConfigurationDTO` | Faible — le DTO d'export est déjà stable, c'est celui de l'import/export back-office | ✅ **retenu** |
|
||
|
||
- [ ] Vérifier que `Export` répond bien à une clé d'API d'app (aujourd'hui utilisé depuis le back-office authentifié)
|
||
- [ ] Documenter le JSON d'export comme contrat public versionné (c'est aussi ce qui servira au mode offline du casque)
|
||
- [ ] Décider si l'export multilingue (aujourd'hui `?language=`) doit renvoyer toutes les langues d'un coup pour la VR — un casque en borne change de langue à chaud
|
||
|
||
### Lot V-4 — L'app Unity + Meta XR SDK — **6-10 semaines** (POC utile : 3-4 semaines)
|
||
|
||
- [ ] **Socle** : Unity 6 LTS, URP, XR Plugin Management → OpenXR + *Meta XR feature group*, Meta XR Core SDK + Interaction SDK (raycast contrôleur **et** hand tracking)
|
||
- [ ] **Couche réseau C#** : appairage par pincode (clone du flux `tablet-app/lib/Screens/Configuration/config_view.dart:274`), récupération de la clé d'API, `POST /api/device` avec `appType = VR` et l'identifiant matériel du casque
|
||
- [ ] **Cache disque + offline-first** : une borne d'accueil perd le wifi ; l'export JSON et les médias se mettent en cache, l'app démarre sur le cache et se rafraîchit en tâche de fond
|
||
- [ ] **Renderer par type de section** — arbitrage tranché, voir §8
|
||
- [ ] **Lecture 360°** : dépend du lot « Ressource 360° » de todo-features.md — sans un type de ressource 360°, il n'y a rien à afficher en immersif
|
||
- [ ] **Mode borne** : verrouiller le casque sur l'app (Meta Horizon Managed Solutions ou un MDM tiers), relance auto au réveil, timeout de session, retour au menu à la repose du casque
|
||
- [ ] **Télémétrie** : `POST` de `VisitEvent` avec `AppType.VR` → les stats manager-app fonctionnent sans une ligne de plus
|
||
- [ ] **Distribution** : Meta Horizon Store (review) ou canal privé / sideload via MDM
|
||
|
||
### Lot V-5 — Supervision de la flotte — **1-2 semaines**
|
||
|
||
- [ ] Client MQTT dans Unity (MQTTnet) : heartbeat, batterie, abonnement à `player/{deviceId}` — le publish côté serveur existe déjà (`DeviceController.cs:303`)
|
||
- [ ] Alternative pour le POC : polling de l'export toutes les N minutes. Moins élégant, 10× moins cher, suffisant pour 1-3 casques
|
||
- [ ] Remontée batterie/version/dernier vu (colonnes déjà présentes sur `Device`)
|
||
|
||
---
|
||
|
||
## 3. Estimation
|
||
|
||
| Lot | Charge | Dépendances |
|
||
|---|---|---|
|
||
| V-1 — Device multi-canal (backend) | 2-3 j | — |
|
||
| V-2 — Onglet XR manager-app | 4-5 j | V-1 |
|
||
| V-3 — Contrat de contenu | 1-2 j | — |
|
||
| **Sous-total « back-office XR prêt »** | **8-12 j** | |
|
||
| V-4 — App Unity (POC : menu + vidéo 360 + une section) | 3-4 sem. | V-1, V-3, Ressource 360° |
|
||
| V-4 — App Unity (couverture complète) | 6-10 sem. | idem |
|
||
| V-5 — Supervision flotte | 1-2 sem. | V-4 |
|
||
| **Total avec app complète** | **~3-4 mois** d'un dev | |
|
||
|
||
Le découpage est volontaire : **les 8-12 jours du back-office XR se tiennent debout tout seuls**.
|
||
Ils rendent le canal VR administrable et démontrable (flotte, assignation de config, stats)
|
||
avant d'engager le vrai coût, qui est l'app Unity.
|
||
|
||
---
|
||
|
||
## 4. Risques
|
||
|
||
1. **Le contenu du CMS est 2D.** C'est le risque produit majeur, pas un risque technique. Un
|
||
Article affiché sur un panneau flottant en VR est *moins* bon qu'une tablette. La valeur du
|
||
canal vient du contenu 360°, que très peu de clients possèdent. Sans un client pilote qui a
|
||
déjà des vidéos 360°, le POC démontrera une régression.
|
||
2. **Nouvelle stack, zéro mutualisation.** Unity/C# XR ne réutilise ni Flutter, ni le client API
|
||
généré, ni `myinfomate_layout`. C'est un quatrième front à maintenir, pas une déclinaison.
|
||
3. ~~**Économie du MDM.**~~ **Rétrogradé le 2026-08-31.** Un MDM XR (~15-20 €/casque/mois) n'est
|
||
nécessaire qu'à l'échelle d'une flotte. À 1-3 casques, sideload adb + le mode appareil intégré
|
||
suffisent. Ne redevient un sujet qu'au-delà d'une dizaine de casques.
|
||
⚠️ Les noms et l'état des programmes business de Meta bougent vite — à revérifier avant de
|
||
l'écrire dans une offre.
|
||
4. **Exploitation en borne publique** : hygiène des casques, sessions courtes, présence de
|
||
personnel. C'est un frein opérationnel réel côté client, indépendant de la qualité de l'app.
|
||
5. **La landing promet déjà « Meta Quest — Bientôt »** en 4 langues
|
||
(`myinfomate-landing`, `translations.ts`, clé `mode4Desc`). L'écart annonce/réalité existe
|
||
depuis un moment ; il se creuse tant que rien n'est livré.
|
||
|
||
---
|
||
|
||
## 5. Recommandation de séquencement
|
||
|
||
1. **V-1 + V-2 + V-3** (~2 semaines) — peu cher, sans risque, rend le canal administrable, ferme le `TODO vr`.
|
||
2. **Ressource 360°** (déjà en V2, le plus petit des trois chantiers médias) — prérequis dur du POC.
|
||
3. **Décision go/no-go** conditionnée à **un client pilote avec du contenu 360° existant**.
|
||
Sans lui, ne pas démarrer V-4.
|
||
4. **POC Unity** (3-4 semaines) — menu immersif + vidéo 360 + une section riche.
|
||
5. V-4 complet et V-5 seulement après validation du POC en conditions réelles.
|
||
|
||
---
|
||
|
||
> **§6 à §10 ajoutés le 2026-08-31**, seconde passe. Tout ce qui suit est vérifié dans le code —
|
||
> c'est de l'analyse déjà faite, à ne pas refaire.
|
||
|
||
---
|
||
|
||
## 6. Pricing — add-on « Contenu immersif », à partir de 70 €/mois
|
||
|
||
**Arbitré par Thomas le 2026-08-31.** Les chiffres encore inscrits dans
|
||
[../roadmap.md](../roadmap.md) — `+30-50€/mois` (L113) et `+40€/mois` dans le bundle (L148-149) —
|
||
sont **périmés** et à réécrire.
|
||
|
||
> Historique de la décision : l'assistant avait proposé un **palier « Immersif » à 299 €/mois**
|
||
> en remplacement de l'add-on. Arbitrage retenu : **on garde un add-on**, revalorisé de 40 à
|
||
> **70 €/mois et plus**. Le raisonnement du palier est conservé ci-dessous parce que **son
|
||
> principe structurant, lui, est validé** — c'est le montant et le véhicule qui changent.
|
||
|
||
### Le principe qui reste vrai : décorréler du casque
|
||
|
||
Un add-on « VR » ne se vend que le jour où l'app Unity existe *et* où le client achète un casque.
|
||
Un add-on **« Contenu immersif »** vend le *contenu* — image 360, vidéo 360, modèles GLB — qui
|
||
s'affiche **déjà dans le web, le mobile et le kiosk**. Le casque devient le haut de gamme de
|
||
l'add-on, pas sa condition d'existence. Un client souscrit et en a pour son argent sans casque.
|
||
|
||
### Pourquoi l'add-on bat le palier — le point que l'assistant avait manqué
|
||
|
||
**Un add-on s'accroche à n'importe quel palier.** Le palier à 299 € forçait tout le monde à passer
|
||
par Premium ; l'add-on permet à un client **Pro à 99 € de prendre du 360 sans payer l'assistant IA
|
||
dont il ne veut pas** → 169 € au lieu de 299 €. Marché adressable nettement plus large.
|
||
|
||
Sur le total, les deux modèles convergeaient de toute façon : Premium 179 + 70 = **249 €**, soit
|
||
exactement le plancher de négociation cité pour le palier. Le désaccord portait sur la structure,
|
||
pas sur le montant.
|
||
|
||
Paliers de référence (source de vérité : `myinfomate-landing`, `HomeClient.tsx:534,583` +
|
||
`segments.ts`) : Essentiel 39 / Pro 99 / Premium 179 / Enterprise sur devis.
|
||
|
||
### Ce que l'add-on débloque
|
||
|
||
Ressources image 360, vidéo 360, modèles GLB avec POI cliquables texte/audio — et l'app Quest
|
||
quand elle sortira. L'assistant IA **n'est pas dedans** : il reste attaché à son propre add-on /
|
||
au palier Premium.
|
||
|
||
### ⚠️ Le stockage — plus aigu en add-on qu'en palier
|
||
|
||
C'est le seul point de vigilance qui **s'aggrave** avec ce modèle. Un client Pro à 99 € a un quota
|
||
dimensionné pour du Pro, et une vidéo 360 de 5 min en qualité correcte pèse des Go.
|
||
|
||
➡️ **L'add-on doit porter sa propre enveloppe de stockage**, explicite et finie, avec facturation
|
||
du dépassement. Sans ça, le premier client 360 sur un petit palier fait sauter la marge.
|
||
|
||
### Ce qui reste HORS de l'add-on
|
||
|
||
| Exclusion | Pourquoi |
|
||
|---|---|
|
||
| Modélisation 3D | Coût studio, 3 à 15 k€, variable — reste **sur devis** |
|
||
| Hardware casque | Achat client |
|
||
| Stockage au-delà de l'enveloppe | Voir ci-dessus — plafond **dur** + facturation du dépassement |
|
||
|
||
Onboarding one-shot : **1200-1500 €** quand il y a des casques (provisioning, config borne,
|
||
formation, journée sur site). Sans casque, l'add-on ne justifie pas de frais de mise en place
|
||
particuliers.
|
||
|
||
### Implémentation — une journée de backend
|
||
|
||
**Vérifié dans le code, et c'est la bonne surprise.** L'`Instance` **recopie localement** les
|
||
valeurs du plan au lieu de les lire à travers : `StorageQuotaBytes`, `AiTokensPerMonth`,
|
||
`HasStats`, `StatsHistoryDays`, `HasAdvancedStats` sont tous des colonnes de `Instance`
|
||
(`Data/Instance.cs:87-98`). Le `SubscriptionPlan` n'est qu'un **gabarit**.
|
||
|
||
Et `IsAssistant` (`Data/Instance.cs:37`) est déjà exactement ce patron : **un add-on = un `bool`
|
||
sur l'`Instance`**.
|
||
|
||
- [ ] `Instance.HasImmersiveContent` (bool) + migration
|
||
- [ ] Relèvement de `StorageQuotaBytes` à l'activation
|
||
- [ ] Exposition dans `InstanceDTO` + l'écran SuperAdmin
|
||
|
||
➡️ **Pas de nouvelle ligne de plan, pas de table de jointure, pas de refonte de la facturation.**
|
||
|
||
**Et ça tombe sur une carte déjà planifiée** : « Écran SuperAdmin — add-on IA & canaux d'une
|
||
instance » (kanban, colonne Planifié), qui dit explicitement « à faire d'un bloc avec l'endpoint
|
||
de mise à jour d'`ApplicationInstance` : c'est le même écran ». L'add-on immersif y prend sa place
|
||
sans rien ajouter au backlog.
|
||
|
||
⚠️ **Réserve Stripe** : le webhook ne gère que `checkout.session.completed` et
|
||
`invoice.payment_failed` (`Controllers/StripeWebhookController.cs:62-65`), rien sur les lignes
|
||
d'abonnement. Facturer l'add-on **automatiquement** demandera du travail. Pour 1-3 clients
|
||
pilotes, une ligne ajoutée à la main dans Stripe suffit.
|
||
|
||
---
|
||
|
||
## 7. Assistant IA dans le casque — possible, et quasi gratuit
|
||
|
||
**Vérifié dans le code.** `POST /api/Ai/chat` (`Controllers/AiController.cs:367`) prend déjà un
|
||
`AppType` dans sa requête (`DTOs/AiChatDTO.cs:10`) et se contente de vérifier que
|
||
l'`ApplicationInstance` de cet `AppType` a `IsAssistant = true` (`AiController.cs:387`).
|
||
|
||
➡️ Créer l'`ApplicationInstance` VR avec le flag suffit. **Zéro ligne de backend.**
|
||
|
||
Deux nuances :
|
||
|
||
- **Pas de fallback pour VR.** Le fallback sur Mobile n'existe que pour `AppType.Voice`
|
||
(commentaire `AiController.cs:387`). Sans `ApplicationInstance` VR, c'est un 403.
|
||
- **Le TTS est côté client.** L'instance ne stocke qu'un nom de voix Gemini (`Data/Instance.cs:54`),
|
||
la synthèse est faite par l'app. Unity devra appeler Gemini TTS lui-même et gérer le micro pour
|
||
le STT. L'architecture est prouvée par le POC Ray-Ban (11 implémentations de moteurs, branche
|
||
`Meta-Rayban-Test`) mais c'est du **Dart** : réimplémentation en C#, pas un portage.
|
||
|
||
---
|
||
|
||
## 8. Types de section en VR — arbitrage tranché
|
||
|
||
Sur les 13 types existants :
|
||
|
||
| Verdict | Types | Pourquoi |
|
||
|---|---|---|
|
||
| **Nécessaire** | Menu | C'est le hub flottant lui-même |
|
||
| **Gagne vraiment** | Video (360 en skybox), Slider (galerie immersive), Map (le plan posé devant soi comme une maquette — c'est là qu'arrive le GLB), Parcours (chaque étape = un lieu autour de soi) | La VR ajoute une dimension que l'écran n'a pas |
|
||
| **Utile en borne** | Event / Agenda | « Ce qui se passe aujourd'hui » en écran d'attente d'office de tourisme — c'est même l'usage principal |
|
||
| **Correct** | Quiz | Jouable au regard ou au contrôleur, bon pour l'engagement |
|
||
| **À ne pas porter** | Article, PDF, Web, Weather, Game | Un mur de texte flottant est *moins* bon qu'une tablette. Une WebView en VR est frustrante |
|
||
|
||
**Scope du POC** : Menu + Video 360 + Slider + Map/GLB, avec Event en écran d'attente.
|
||
Quatre types, et c'est exactement la démo.
|
||
|
||
---
|
||
|
||
## 9. POI sur modèle GLB — le modèle de données existe déjà à 80 %
|
||
|
||
**La plus grosse économie du chantier, repérée le 2026-08-31.**
|
||
|
||
`GeoPoint` (`Data/SubSection/SectionMap.cs:85`) porte déjà `Title`, `Description`, `ImageResource`,
|
||
et surtout **`Contents`** — une `List<Content>` où chaque `Content` (`Data/SubSection/Content.cs`)
|
||
est titre + description + **`Resource`**, donc **audio**. Le tout multilingue via `TranslationDTO`.
|
||
|
||
➡️ Un POI sur GLB, c'est **le même objet avec une position locale (x,y,z) au lieu d'une lat/lon**.
|
||
|
||
- [ ] `GeoPoint.Model3DPosition` nullable (x,y,z + rotation) à côté du `Geometry` PostGIS existant
|
||
- [ ] `SectionModel3D` référençant une ressource GLB
|
||
- [ ] `ResourceType.Model3D` — **en fin d'enum**, il est persisté en int
|
||
- [ ] Éditeur de placement de POI en 3D dans manager-app — **le seul morceau non trivial**
|
||
|
||
Le back-office réutilise l'éditeur de POI existant ; le multilingue et l'audio suivent gratuitement.
|
||
|
||
---
|
||
|
||
## 10. Unity ou WebXR — la question tranchée
|
||
|
||
### Unity n'est PAS une nécessité technique
|
||
|
||
La distinction qui compte : **le mode kiosk ne vient pas du moteur, il vient de la gestion
|
||
d'appareil.** Un MDM ou le mode appareil de Meta verrouille le casque sur *n'importe quelle* app
|
||
installée — build Unity, APK Godot, ou navigateur épinglé sur une URL. Le choix du moteur est
|
||
**orthogonal** au verrouillage kiosk.
|
||
|
||
Routes possibles vers un APK installable sur Quest : Unity + Meta XR SDK, Unreal, Godot 4 + OpenXR,
|
||
ou du WebXR empaqueté (Meta supporte l'installation de PWA sur Horizon OS).
|
||
⚠️ Sur ce dernier point : hors date de connaissance de l'assistant, **à revérifier** avant
|
||
tout engagement.
|
||
|
||
### Ce qui plaide pour Unity — l'exploitation, pas la technique
|
||
|
||
- **Média offline** : plusieurs Go de vidéo 360 en cache navigateur se heurtent aux quotas de
|
||
stockage. En natif on écrit sur le disque.
|
||
- **Décodage vidéo** : une 360 en 8K veut le décodeur matériel et le contrôle dessus.
|
||
- **Prévisibilité** — *l'argument le plus fort* : une borne tourne 8 h/jour sans personne. Un build
|
||
Unity est figé ; le navigateur du casque se met à jour tout seul et peut casser la borne un mardi
|
||
matin.
|
||
- Accessoirement : MQTT en tâche de fond, hand tracking riche, porte du Horizon Store.
|
||
|
||
### Ce qui plaide pour WebXR — à ne pas balayer
|
||
|
||
`visitapp-web` rend **déjà les 13 types de section** en React 19 / Next 16
|
||
(`src/components/sections/`, 13 fichiers, aucune dépendance 3D pour l'instant). Une couche WebXR
|
||
par-dessus **attaque frontalement le risque n°2** (« nouvelle stack, zéro mutualisation ») :
|
||
c'est potentiellement un quatrième front en moins à maintenir.
|
||
|
||
### Position retenue
|
||
|
||
**WebXR pour la maquette et la spec visuelle, Unity pour la borne en production.**
|
||
Pas parce que le web ne peut pas, mais parce qu'une borne publique non surveillée pardonne mal.
|
||
➡️ **À rouvrir si** l'usage devient « le visiteur met le casque 5 minutes avec un agent à côté » —
|
||
là, le web redevient le bon choix.
|
||
|
||
### Ce que l'assistant peut et ne peut pas produire
|
||
|
||
**Peut** : le projet Unity complet — scripts C#, client API, script d'éditeur qui construit la scène
|
||
par code (plutôt que du YAML `.unity` écrit à la main, illisible et cassant), `manifest.json` avec
|
||
les packages Meta XR, menu flottant, swap de skybox 360, chargement GLB, raycast sur POI avec audio.
|
||
|
||
**Ne peut pas** : ⚠️ **Unity n'est pas installé sur cette machine** — vérifié le 2026-08-31, ni
|
||
`Program Files/Unity`, ni Unity Hub. Donc pas de compilation, pas d'APK, pas de rendu, pas de
|
||
casque de test. Le livrable est un projet *plausible* : il faut l'ouvrir et itérer sur les erreurs
|
||
de compilation. **Compter 2 à 4 allers-retours**, surtout sur le SDK Meta dont les API bougent
|
||
entre versions.
|
||
|
||
Pour *voir* le rendu, la maquette WebXR/three.js est le chemin court : visible sur écran ou dans le
|
||
navigateur du Quest en immersif, itérable en secondes. Précédent dans le repo : la démo villa
|
||
d'Echternach (`visitapp-web/src/app/demo/villa-echternach/scene.ts`), rendu procédural fait
|
||
exactement pour montrer au client.
|