Même borne, même durée, +etm61 : commandée 13 à 20 A au lieu de 9, mesurée
8,4 kW au lieu de 4,4, et 716 Wh livrés en 5 minutes contre 276 en 10. Le motif
dit maintenant « 13800 W achetés sur 13800 W possibles » et boundBy vaut
loadCeiling — le plafond suit le soutirage de la maison d'un cycle à l'autre,
parce que evPhaseAllowanceW() le recalcule sur la bonne grandeur.
Trois observations gardées, dont une pour l'étape 4 : la FERMETURE d'une
dérogation ouvre elle aussi une divergence (attendu 0 W, mesuré 8367 W), et
elle est plus trompeuse que celle d'ouverture. Une divergence qui naît dans le
même cycle qu'un DEROGATION_ENDED est une descente en cours, jamais une
commande perdue — sans quoi chaque boost compterait pour un incident.
La fin par débranchement reste non éprouvée : la voiture est revenue avant les
deux confirmations. Le mécanisme a en revanche montré ce qu'on lui demande —
une absence isolée n'a PAS terminé la dérogation (LM-1500-c).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
Deux défauts trouvés par le premier essai au banc (2026-09-08, 11:05→11:16),
et le premier est celui que Patrick a vu en regardant la voiture.
LM-1500-h — UNE BORNE N'EST PAS ÉCRÊTÉE DEUX FOIS.
La tête de passe éco appliquait `margeRestante` au plafond d'une borne. Or
`margeRestante` est la marge d'UNE phase, et le plafond d'une borne triphasée
est une enveloppe sur TROIS : les comparer divise son allocation par trois.
Mesuré : marge de 6 210 W sur la phase la plus chargée, plafond de cycle à
13 800 W, borne servie à 6 210 — 9 A par phase au lieu de 27, 4 kW au lieu des
11 que la voiture pouvait prendre.
Le plafond d'une borne porte DÉJÀ l'autorisation de soutirage :
evPhaseAllowanceW() la calcule et la multiplie par le nombre de phases
occupées. La passe confort ne fait pas cette faute, et son commentaire dit
pourquoi depuis 3g-2 — « mieux vaut la borne mieux informée que la borne la
plus sévère ». Je l'ai réécrite quinze lignes plus haut sans la relire : une
règle notée à un seul endroit du code n'est pas une règle.
L'erreur allait dans le sens PRUDENT, donc rien ne la signalait — ni le
disjoncteur, ni le délestage, ni un test qui vérifie qu'on ne dépasse pas. Elle
ne se voyait qu'en regardant la voiture.
LM-1500-g — ÉCRIRE UN MODE PENDANT UNE DÉROGATION LA TERMINE.
Choisir un mode est durable ; une dérogation répond à un besoin ponctuel. Le
durable prime, et il prime tout de suite. Et SANS restituer le mode mémorisé,
qui écraserait celui que l'utilisateur vient de choisir.
Le cas vient du banc : le bouton « boost » de l'app écrit encore
ChargingModeNormal. Reçu pendant une dérogation, il fait restituer les valeurs
manuelles par l'amont — chargingEnabled à FAUX — et arme un verrou de 300 s,
pendant que la dérogation continue de commander 9 A. Deux commandeurs pour un
organe, et six minutes de dérogation servie sur le papier dans une borne
éteinte. Seul le journal de divergence le voyait, sans pouvoir en dire la cause.
Deux autres corrections du même essai :
- DEROGATION_ACTIVE et DEROGATION_ENDED sont rendus en français au journal ;
ils sortaient en « (motif inconnu …) ». Le repli 7-c a tenu — le code brut et
ses paramètres, jamais une chaîne vide — mais la ligne lue par l'utilisateur
était illisible.
- poseDerogation() prend l'instant en paramètre. Elle lisait l'horloge murale
quand le cycle lit l'horloge injectée : sans effet en production, où les deux
sont la même, mais sous test la fin par durée n'arrivait jamais. Trouvé par le
test, pas par la lecture.
Et `setChargingInfo() déclenche un cycle` est gravé dans AGENTS.md comme une
PROPRIÉTÉ : ce qui l'entoure a un ordre qui compte, dans les deux sens, et
ENERGY_SIMULATION supprime précisément l'appel — le chemin fautif n'existe pas
sous test.
simulation 69/69, loadmodel 26/26, charging 48/48, spotmarket 32/32,
ci-quality 4/4, build 0 erreur 0 warning.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
setChargingInfo() appelle update() lui-même, et le lot le touche aux deux bouts.
À LA POSE, l'ordre décide d'une minute. L'écriture du mode vient désormais en
DERNIER, après l'insertion dans m_derogations : le cycle qu'elle déclenche
trouve la dérogation déjà posée et la sert immédiatement. L'ordre inverse la
lui faisait manquer d'un cycle — soixante secondes d'attente sur un mécanisme
dont la définition est « tout de suite ».
À LA FIN, la restitution est différée d'un tour de boucle. Les fins sont
évaluées DEPUIS update() ; y appeler setChargingInfo() imbriquait un update()
dans un update() — deux plans construits et deux dispatchs pour le même
instant, sur des charges qu'un seul des deux croirait avoir commandées.
update() n'a aucun verrou de ré-entrance : rien ne l'aurait signalé, et la
trace aurait montré une borne commandée deux fois sans cause lisible.
simulation 68/68, ci-quality 4/4, build 0 erreur 0 warning.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
Une dérogation n'est pas un mode. Un mode se choisit une fois et vaut jusqu'au
prochain choix ; une dérogation répond à un besoin ponctuel et s'éteint avec
lui. Un boost encore actif après la fin est un mode qu'on a oublié de quitter.
Elle se superpose au mode courant, sert la charge à son MAXIMUM en tête de
passe éco — devant toute autre charge, quel que soit son rang — financée au
réseau. Le rang classe ce que l'installateur décide (LM-1002) ; une dérogation
est une action ponctuelle de l'utilisateur, et passe par une porte que LM-1002
ne ferme pas.
Mais elle ne surplombe PAS la sûreté : DRAW_CAP la borne et le délestage la
reprend. Servie la première dans ce qui RESTE, jamais au-delà — sans quoi on
rouvrirait le second chemin d'achat refusé au minimum réseau (LM-1500-e).
LA DURÉE EST OBLIGATOIRE ET BORNÉE, refusée à la pose hors de [60 s, 6 h].
C'est la règle du lot : une dérogation qui ne sait pas dire quand elle finit ne
devrait pas exister (LM-1500). Elle reste armée même avec une cible, parce
qu'une cible repose sur un instrument et qu'un instrument peut se taire — à la
perte du lien, la V2C FIGE sessionEnergy au lieu de l'effacer (LM-1012-d) : une
dérogation « 20 kWh puis stop » dont le compteur reste à 12 kWh n'atteint
jamais sa cible.
Trois décisions prises en écrivant, absentes du design :
- Les fins se lisent dans le CONTEXTE DU CYCLE, pas dans les adaptateurs.
evaluerDerogations() tourne entre buildContext() et getPlan(), sur ce que le
scheduler va lire. Une seconde lecture donnerait une seconde vérité, et une
dérogation pourrait finir sur une mesure que le plan ne voit pas. C'est aussi
ce qui rend le débranchement gratuit : la borne est déjà filtrée du contexte,
son absence EST le signal.
- measurementLost ne mord QUE s'il y a une cible (LM-1500-b). Sans cible, rien
ne dépend de la mesure : couper là punirait la charge du silence d'un
instrument dont elle n'a pas besoin. Et le critère est
telemetry.sessionMeasurable, jamais progress.regime : `progress` est absent
d'une charge sans obligation en énergie, c'est-à-dire exactement le cas du
boost — le critère aurait été inerte.
- La base de la cible se capture au PREMIER CYCLE, pas à la pose : au moment où
l'utilisateur appuie, aucun cycle n'a construit de contexte. Prendre 0
compterait depuis le début de la session — une dérogation « 5 kWh » posée sur
une session déjà à 6 kWh serait atteinte avant d'avoir commencé (LM-1012-b).
Le mode précédent se MÉMORISE, il ne se déduit pas (LM-1500-d). Revenir à un
défaut détruirait un réglage en silence, et sur une borne la sortirait de
l'arbitrage : une dérogation qui laisse l'installation moins pilotée qu'avant
elle. Une borne en manuel est basculée en éco pour la durée — seul cas où le
mode est écrit, et modeForce retient qu'on l'a fait.
La passe confort est SAUTÉE sous dérogation : la fusion des niveaux est une
addition, et la dérogation demande déjà le maximum. Conséquence comptable à
connaître : elle est comptée entièrement en achat alors que le surplus présent
la finance en partie — gridW la surestime au soleil. Approximation héritée du
§10, non aggravée, et la raison pour laquelle la première vérification au banc
se fait le matin.
La fin est un ÉVÉNEMENT et dit sa CAUSE (LM-1500-f), publiée dans
derogationEnded[] au NIVEAU DE LA TRAME : au débranchement la charge n'est déjà
plus dans loads[], et l'événement disparaîtrait quand il explique le plus de
choses. Cinq causes, cinq gestes. Le mode dégradé la porte aussi — c'est là que
measurementLost se produit. telemetrySignificantlyDiffers ne la filtre jamais :
un événement ne vit qu'un cycle.
Éprouvé par testDerogationTakesTheHeadButNotTheSafety, dont la tête de passe est
prouvée par la RARETÉ : 7 500 W d'autorisation pour 8 000 demandés, la borne de
rang 9 servie ENTIÈREMENT et l'ECS de rang 1 réduite aux 500 restants — l'inverse
exact de l'ordre des rangs. Contre-épreuve : la même borne sans dérogation
reçoit 0 W. Et à autorisation nulle, AUCUN niveau — pas un niveau à zéro.
simulation 68/68, loadmodel 26/26, charging 48/48, spotmarket 32/32,
ci-quality 4/4, build 0 erreur 0 warning.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
LA QUESTION QUE NI L'UN NI L'AUTRE N'AVAIT POSÉE : que fait la dérogation ENTRE le
figement de sessionEnergy et l'échéance de durée ? Elle achète au maximum sur une
mesure morte, et sur un lien qui perd 40 % de ses trames « au bout » peut vouloir
dire des heures.
POSITION RETENUE : une dérogation dont la cible cesse d'être mesurable CESSE. Elle
n'attend pas sa durée. C'est R6 transposée — là-bas « servie oui, tenue jamais » ;
ici « active oui, vérifiable non, donc on s'arrête ». Une dérogation est une
commande qui ACHÈTE, et la maintenir sans savoir où elle en est, c'est acheter à
l'aveugle. L'instrument existe déjà : measurement.source et le régime de LM-1009,
qui le disent au cycle même.
QUATRE FINS, et le motif dit laquelle a mordu : targetReached, unplugged,
measurementLost, durationElapsed. Les quatre appellent des gestes différents — et
une dérogation qui se terminerait sans dire laquelle laisserait l'utilisateur
relancer un boost que le lien empêchera encore.
L'ASYMÉTRIE, reprise du plugin V2C. La confirmation de débranchement existe
(k_absencesPourClore polls) et porte une propriété qu'il faut garder : SEULS LES
POLLS RÉUSSIS font avancer le compteur d'absences. Une coupure réseau le gèle au
lieu de le faire progresser — un lien mort ne peut donc pas fabriquer un faux
débranchement.
Et les deux erreurs ne coûtent pas la même chose : terminer à tort rend la main,
continuer à tort achète au maximum sur une mesure morte. D'où la règle, gravée dans
AGENTS.md : un mécanisme qui achète s'arrête dans le doute, et ses seuils de
confirmation sont asymétriques par conception — prompts à conclure la fin, lents à
conclure la poursuite. C'est l'inverse du réglage qu'on choisirait pour un mode.
La durée devient le FILET DU FILET : la perte de mesure attrape le cas où
l'instrument se tait et se signale ; la durée attrape le cas où même le régime ne
se signale pas. Deux filets parce que le premier repose lui-même sur un instrument.
Correction de Patrick, et elle supprime un manque au lieu de le documenter.
PAS DE COMMUNICATION VÉHICULE, et il n'y en aura pas : aucun plugin constructeur
prévu, et une intégration cloud Skoda/VW serait contraire au 100 % local, fragile,
rafraîchie à la minute au mieux.
Mais l'énergie est la BONNE grandeur indépendamment de cette contrainte. « 20 kWh
puis tu t'arrêtes » demande sessionEnergy, que le plugin V2C construit depuis le
2026-08-29. « Jusqu'à 80 % » demanderait la capacité, l'état de santé et le
rendement — trois inconnues pour dire la même chose en moins sûr. C'est exactement
ce que LM-1009 a établi pour l'obligation éco : on mesure de l'énergie parce que
c'est ce qu'on sait mesurer.
La fin par SOC ne figure donc plus dans le design, et son absence est écrite comme
une DÉCISION, pas comme un manque — sinon quelqu'un l'ouvrirait comme un lot.
ET sessionEnergy A UN SILENCE CONNU, qui justifie le filet mieux qu'un cas d'école.
LM-1012-d, mesuré sur la V2C le 2026-08-30 : à la perte du lien, currentPower tombe
à 0 et les booléens à false, mais sessionEnergy RESTE À SA DERNIÈRE VALEUR. Une
dérogation « 20 kWh puis stop » dont le compteur se fige à 12 kWh n'atteindra
jamais sa cible et achèterait au maximum indéfiniment. C'est le comportement
documenté de la borne installée, pas une hypothèse.
Conséquence heureuse : les trois fins du VE — débranchement, cible en énergie,
durée — sont toutes exerçables sans véhicule communicant. Ce lot est éprouvable sur
le banc réel, là où une fin par SoC ne l'aurait jamais été.
CADRAGE DE PATRICK, et il vaut au-delà du VE. Un mode se choisit une fois et vaut
jusqu'au prochain choix ; une dérogation répond à un besoin ponctuel et s'éteint
avec lui. Un boost encore actif après la fin est un mode qu'on a oublié de
quitter ; un boost qui rend la main est une dérogation terminée.
Conséquence de conception : ni un ChargingMode de plus, ni une demande dans needs.
Une dérogation se superpose au mode courant et s'efface. Elle n'a ni cible
d'énergie ni échéance — les deux supposent un besoin qui dure.
CE QUI LA BORNE : la sûreté seule, DRAW_CAP et le délestage, qui deviennent la
seule limite donc une limite obligatoire. Elle prend la tête de la passe éco mais
ne surplombe pas la sûreté : si DRAW_CAP mord, elle est servie la première DANS CE
QUI RESTE — sans quoi on retomberait sur le second chemin d'achat refusé pour le
minimum.
LE RANG. Le boost passe devant, y compris devant une charge mieux rangée. LM-1002
dit que le rang est ce que l'INSTALLATEUR décide ; elle ne dit rien d'une action
ponctuelle de l'utilisateur. Prendre la tête passe par une porte qu'elle ne ferme
pas.
DEUX PRÉCAUTIONS. Le mode précédent se MÉMORISE : retomber sur Normal sortirait la
borne de l'arbitrage, l'inverse du but — même famille que le repli d'échéance qui
réécrivait EcoWithMinCurrent en Eco. Et « terminé » se définit PAR MÉCANISME, ce
que le moteur ne peut pas deviner : c'est un ajout à ILoadAdapter, au même titre
que commandSettleS().
LA RÈGLE, gravée dans AGENTS.md : une dérogation qui ne sait pas dire quand elle
finit ne devrait pas exister. Sur un chauffe-eau sans sonde, « jusqu'à ce que ce
soit chaud » n'est pas mesurable — la fin doit être une durée, explicitement, et se
dire À LA SAISIE. Même raisonnement que progressMeasurable : l'écran de résultat
parle trop tard.
ET LA DURÉE EST UN FILET, jamais une option. Toute dérogation en porte une, même
avec une meilleure condition de fin, parce que cette condition repose sur un
INSTRUMENT et qu'un instrument peut se taire. Une cible qui ne remonte plus, une
sonde qui tombe : la dérogation ne finit jamais, en commandant le maximum. C'est
l'instrument dont le silence ressemble à un résultat, appliqué à une commande qui
achète. Donc jamais « jusqu'à annulation ».