diff --git a/docs/BRIEF_plugin_depuis_maquettes.md b/docs/BRIEF_plugin_depuis_maquettes.md
index 4546b8d..797d436 100644
--- a/docs/BRIEF_plugin_depuis_maquettes.md
+++ b/docs/BRIEF_plugin_depuis_maquettes.md
@@ -129,9 +129,22 @@ combinatoire de `relayrouter.cpp` (toutes les combinaisons 2^N, doublons écart
16 relais). Deux implémentations d'une même règle divergeront : à la première correction
côté moteur, l'app annoncera des paliers que la box n'atteint plus.
-**Demande.** Exposer la liste — soit dans la télémétrie (`mechanism.stagesW: [...]`), soit en
-retour d'un `GetLoadConfig` enrichi. À défaut, **le dire explicitement**, et l'app
-documentera sa dérivation comme un miroir à resynchroniser.
+**⚠️ Tranché par la rév. 3 du contrat, arrivée pendant la rédaction de ce brief.**
+`INTERFACE_etmvariableload.md` §10 dit désormais : l'assistant calcule et **affiche** les
+paliers atteignables côté app, et ces `powerLevels` ne sont **pas** envoyés — ils sont
+« dérivés côté plugin ». La duplication est donc **voulue et contractuelle** : l'app dérive
+pour montrer, le routeur dérive pour exécuter.
+
+**Ce qui reste, et qui devient la vraie demande :** un moyen de vérifier que les deux
+dérivations n'ont pas divergé. Il existe déjà à moitié — la télémétrie publie
+`mechanism.maxStageW`. L'app peut donc comparer **le maximum de sa propre liste** à celui que
+la box annonce, et se taire si ça correspond, alerter sinon. Ça ne couvre pas les paliers
+intermédiaires, mais ça attrape le cas qui compte : une règle de combinatoire changée d'un
+côté seulement.
+
+**Demande minimale** : confirmer que `maxStageW` reste bien le maximum atteignable et non un
+plafond de sécurité — sinon la comparaison ne vaut rien. **Demande confortable** : publier la
+liste complète (`mechanism.stagesW: [...]`), et l'app cessera de dériver.
---
diff --git a/docs/mockups/configurer_mecanismes_mockup.html b/docs/mockups/configurer_mecanismes_mockup.html
index 5e61822..729de82 100644
--- a/docs/mockups/configurer_mecanismes_mockup.html
+++ b/docs/mockups/configurer_mecanismes_mockup.html
@@ -288,6 +288,9 @@
créent pas de palier supplémentaire — les doublons sont écartés.
+
Le picker liste les things par NOM, filtrés sur
+ l'interface nymea power — taper un UUID au doigt est une erreur
+ garantie. Il résout nom → ThingId et n'ajoute aucun champ au contrat.
Un ECS simple, c'est un seul relais — deux paliers, 0 et P. Une
résistance multipalier en déclare 2, 3 ou 4. La puissance saisie est celle de la
résistance, pas le calibre du contacteur : rien dans nymea ne dit ce qu'un relais
diff --git a/tools/devlog/vm_logs.dart b/tools/devlog/vm_logs.dart
index 83078ab..566c0b5 100644
--- a/tools/devlog/vm_logs.dart
+++ b/tools/devlog/vm_logs.dart
@@ -2,14 +2,32 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
-// Capte les traces `dart:developer log()` de l'app en cours d'exécution.
+// Pont VM Service — rend visibles les traces `dart:developer log()` de l'app.
//
-// `flutter run` ne les imprime PAS sur stdout (elles partent au VM Service, pas à
-// stdout ni à logcat). Sans ce pont, tout ce que `NymeaService._log()` journalise est
-// invisible depuis le poste — donc les traces de locale, de télémétrie et d'écriture
-// aussi.
+// ## Pourquoi ce fichier existe
//
-// Usage : dart vm_logs.dart ws://127.0.0.1:PORT/TOKEN=/ws
+// `flutter run` **n'imprime pas** ces traces sur stdout : elles partent au VM Service, ni sur
+// la console, ni dans `logcat`. Tout ce que `NymeaService._log()` journalise — locale demandée
+// et retenue, aller-retours RPC, séquence d'écriture, télémétrie poussée — est donc invisible
+// depuis le poste tant qu'on n'est pas branché ici.
+//
+// Éprouvé le 2026-08-25, premier passage de l'app sur appareil (Redmi Note 9S) : sans ce pont,
+// aucun constat du journal de vérification n'était atteignable.
+//
+// ## Usage
+//
+// flutter run -d --use-application-binary=build/app/outputs/flutter-apk/app-debug.apk
+// # relever « A Dart VM Service on … is available at: http://127.0.0.1:PORT/TOKEN=/ »
+// dart tools/devlog/vm_logs.dart 'ws://127.0.0.1:PORT/TOKEN=/ws'
+//
+// Le `/ws` final est obligatoire, et les guillemets aussi — le jeton contient un `=`.
+//
+// ## Le piège d'horodatage, déjà payé une fois
+//
+// Le VM Service livre **l'historique bufférisé d'un coup** au moment du `streamListen`.
+// Horodater à la réception écrase donc toute la chronologie sous une seule seconde — c'est ce
+// qui est arrivé au premier essai. On lit `event.timestamp`, jamais `DateTime.now()`.
+
Future main(List args) async {
final uri = args.first;
final ws = await WebSocket.connect(uri);