Préalable au reste, et il n'ajoute aucune fonction. La chaîne de sélection
du motif vivait au milieu de l'émission : il n'y avait qu'un motif possible
parce qu'il n'y avait qu'un endroit pour le former. Chaque passe devra
choisir SON motif sur SES grandeurs (R7 — le motif dit de quel niveau il
parle), donc la chaîne sort avant que la seconde passe n'arrive.
`MotifEntrees` regroupe exactement ce dont le choix dépend : ce qui n'y
figure pas ne peut pas influencer le motif, et c'est vérifiable à l'œil.
Comportement CONSTANT, et vérifié comme tel : la fonction extraite est
IDENTIQUE TOKEN POUR TOKEN à la chaîne d'origine, comparée par
normalisation. L'ordre des branches est ce qui porte les précédences
(LM-1011 : BELOW_MIN_POWER prime sur DRAW_CAP tant qu'il reste de
l'autorisation) — le réordonner changerait le motif publié sans qu'aucun
nombre ne bouge.
Deux erreurs attrapées en chemin, toutes deux par relecture :
- la substitution avait préfixé les CLÉS de charge utile ("budgetW" →
"e.budgetW"), soit un changement de contrat silencieux ;
- un `else if` transformé en `if` : sans effet ici puisque chaque branche
retourne, mais une divergence gratuite dans une fonction dont l'ordre
des branches est la spécification.
Et un piège de HARNAIS, sans rapport avec l'étape mais qui la bloquait :
testL4ShedsAPacAndSaysItForcedTheLock échouait 5 fois sur 5 alors qu'il
passait la veille, à code identique. Ni le code sous test, ni une fragilité
de temporisation — un cycle d'arbitrage tourne à l'horloge MURALE avant le
premier cycle simulé (sonde : action SG_NORMAL estampillée « 22:52:07 »).
L'armement paresseux d'ECS-412 fige m_lastSwitch sur cette heure, et un
scénario daté en dur se déroule « avant » son propre armement : elapsed
négatif, verrou jamais ouvert. Le test passait ou échouait SELON L'HEURE
QU'IL EST, ce qui est pire que les deux, et c'est aussi l'explication du
remainingS: 1288 observé plus tôt sans qu'on sache le lire.
Corrigé en ancrant l'origine du scénario sur l'horloge réelle : la
chronologie relative suffit, l'origine doit vivre dans la même horloge que
ce qui touche le système. Le commentaire dit à quelle condition ce choix
devient faux, conformément au troisième mode d'érosion.
Reste ouvert et consigné : pourquoi un cycle complet s'exécute à l'horloge
murale dans une suite compilée avec ENERGY_SIMULATION, où les minuteurs sont
explicitement exclus. Pas un défaut de production — une seule horloge y
existe — mais une faille de reproductibilité pour tout test manipulant un
Thing.
Suite complète : simulation 54/54, charging 48/48 identique à la référence
ligne à ligne, loadmodel 21/21, spotmarket 32/32, doxygen 0 avertissement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
6.2 KiB
RELEVÉ — trois constats de l'agent app, à traiter APRÈS le §10
(Remontés le 2026-08-31, mesurés sur .75. Rangés ici pour ne pas se perdre : ils ne passent
pas devant le §10, et aucun ne se déduit du code — les trois viennent de la machine.)
1. Une commande perdue pendant une indisponibilité n'est JAMAIS réappliquée
Mesuré. La box journalise Restoring manual values of false 6 A trois secondes après une
coupure Modbus. La borne, elle, reste à power: true, 7 A — ce que l'arbitre lui avait
commandé au cycle précédent. Le lien revient : rien ne réapplique. En mode manuel personne ne
commande, donc l'écart est permanent.
Ce que ça coûte, chiffré : 4,4 kW réellement tirés, draw.committedW à 0, et un budget de
surplus de 2 349 W qui ignore ces watts.
Une coupure d'une seconde suffit donc à laisser une borne à un courant que plus personne n'a choisi, et le budget est faux tant que ça dure. Sur 18 kVA c'est invisible ; sur 6 kVA c'est le disjoncteur.
La question posée par l'app était étroite — « une entrée en mode manuel devrait-elle réappliquer ses valeurs à la reconnexion du Thing ? ». Elle est élargie : toute commande perdue pendant une indisponibilité devrait être réappliquée au retour, ou la divergence signalée.
Et c'est calculable dès aujourd'hui. On sait ce qu'on a commandé, la borne dit ce qu'elle
fait, et measurement.source (LM-1105 / LM-1106) dit désormais ce que vaut la
comparaison — une divergence entre commandé et device n'a pas le même statut qu'une
divergence contre none.
Même famille que toute la semaine : le lien revient, l'état a l'air sain,
availableest vrai, et personne ne sait que la commande n'a jamais atterri. C'estECS-111etECS-415sous une troisième forme — après la télémétrie et après la valeur de retour, voici l'exécution.
2. chargingState reste ChargingStateIdle pendant une charge RÉELLE
Mesuré. Lien vivant, borne publiant 4 372 → 4 397 W, courants qui bougent — et le champ reste
à Idle.
Ce n'est donc PAS de la péremption, et c'est ce qui rend le constat utile : personne ne maintient le champ quand l'arbitre ne commande plus la borne.
Un champ que plus personne ne maintient devrait être ABSENT, pas figé. Figé, un écran affiche
« à l'arrêt » pendant une charge. C'est l'invariant 12 d'AGENTS.md — un champ absent vaut mieux
qu'un champ nul ou forgé — appliqué à un champ dont le mainteneur a disparu, et non la valeur.
3. ChargingModeNormal n'est pas un boost, et son nom le laisse croire
Il restaure des « valeurs manuelles mémorisées », qui peuvent être power: false, 6 A. Y
passer peut donc arrêter une charge alors que l'utilisateur croit demander la pleine
puissance. Le nom du mode et son effet ne coïncident pas.
À documenter au minimum. À corriger si l'on juge que le nom peut bouger — c'est une frontière amont, et un renommage se paie côté clients.
Et un point OUVERT qui se CLÔT : la V2C publie sessionEnergy
Mesuré, deux échantillons à deux heures d'écart sur charge réelle : l'écart entre
sessionEnergy et chargeEnergy est constant au dix-millième — 7,2044 dans les deux cas.
C'est l'accumulateur du plugin V2C qui porte les incréments d'avant la dernière remise à
zéro, et ça ne se déduisait pas du code.
Conséquences, et elles touchent l'étape C du §10 :
LM-1009 C1reste vraie pour les bornes qui n'ont pas l'état — mais la V2C n'en fait plus partie. Le cas « réel dès aujourd'hui » qui motivait R6 change de porteur.sessionEnergyest l'accumulateur à lire pour alimenterLoadContextTelemetry::sessionWh.- ⚠️ Il porte un OFFSET. Sa valeur absolue n'est pas l'énergie de la session courante.
deliveredWhdoit donc être une différence depuis une base capturée, jamais une lecture directe — sinon toute obligation éco se croirait déjà tenue de 7,2 kWh au premier cycle. C'est le genre d'erreur qui produit un nombre crédible et faux, et que rien ne signalerait.
L'app a corrigé sa maquette et son modèle en conséquence.
ANNEXE — un piège de harnais, découvert en faisant l'étape A du §10
(2026-08-30, en cherchant pourquoi un test vert la veille échouait déterministiquement.)
testL4ShedsAPacAndSaysItForcedTheLock échouait cinq fois sur cinq, et la cause n'était ni
le code sous test ni une fragilité de temporisation : un cycle d'arbitrage tourne à l'horloge
MURALE avant le premier cycle simulé. Relevé par sonde : la PAC reçoit une action SG_NORMAL
estampillée 22:52:07 — l'heure qu'il était.
L'armement paresseux d'ECS-412 fige alors m_lastSwitch sur cette heure. Un scénario daté en
dur (13:00 simulé) se déroule donc « avant » son propre armement : elapsed est négatif, le
verrou ne s'ouvre jamais, la PAC n'atteint pas l'état 4, et la prémisse du test s'effondre.
Le test passait ou échouait selon l'heure de la journée — ce qui est pire que les deux, et ce qui explique aussi le
remainingS: 1288observé plus tôt sans qu'on sache l'expliquer.
Correctif retenu : ancrer l'origine du scénario sur QDateTime::currentDateTimeUtc(). La
chronologie relative est tout ce dont il a besoin ; l'origine doit vivre dans la même horloge
que ce qui le touche. Le commentaire dit à quelle condition ce choix devient faux — le jour où
l'arbitrage ne peut plus tourner à l'horloge murale dans la suite, une date fixe redevient
préférable parce qu'elle rend les journaux reproductibles.
Ce qui reste ouvert, et ne se corrige pas dans un lot de test : pourquoi un cycle
d'arbitrage complet s'exécute-t-il à l'horloge murale dans une suite compilée avec
ENERGY_SIMULATION, alors que les minuteurs et les connexions temps réel y sont explicitement
exclus (energyarbitrator.cpp:52-75) ? Un changement d'état de Thing suffit à le déclencher. Ce
n'est pas un défaut de production — il n'y a là-bas qu'une seule horloge — mais c'est une
faille de reproductibilité qui touchera tout test de simulation manipulant un Thing.