Moteur de 6481515 avec les scénarios de ff7c801, contre l'ancienne référence :
IDENTIQUE HORS MOTIFS ET HORS ACHAT (comparateur --achat) — 23 772 lignes, 23 765 identiques à
l'octet, 7 qui ne diffèrent que par progress.purchasedWh, toutes « 0 → absent » (régime
unmeasurable). Aucune consigne ne change. S'y ajoutent les 20 lignes du nouveau
testPurchasedIsCountedOnTheMeasuredEnergy : 68 fonctions, 23 792 lignes. check : IDENTIQUE, 0 rejeu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le correctif c change une VALEUR PUBLIÉE (progress.purchasedWh), pas une consigne. --achat
retire en plus ce champ de chaque niveau et dit, par scénario et charge, l'ancienne et la
nouvelle valeur (ou « absent ») ; --lignes écrit chaque ligne qui change. Sans option, le
comportement d'etm70 est inchangé.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`noterAchatEco()` intégrait la CIBLE éco avant palier et verrou : sur un relay-router, une
cible de 167 ou 495 W laisse le relais ouvert et ces watts étaient comptés achetés ; sous un
optimiseur, l'ombre rule-based notait sa propre cible, jamais exécutée.
Règle (choix par défaut du brief) : achatWh += ΔE × partÉco, ΔE = énergie MESURÉE de la charge
depuis le cycle précédent (sessionWh), partÉco = min(1, Σ targetW « eco » / estimatedPowerW)
de l'action APPLIQUÉE au cycle précédent (m_actionsAppliquees, lue avant le dispatch). Niveau
unique `optimizer`, charge délestée ou rien d'appliqué : part 0. Un recul ne contribue jamais.
- Sans mesure d'énergie : purchasedWh ABSENT (EcoProgress::purchased), jamais 0 ni la commande.
Non borné par l'import du site : attribution ambiguë avec plusieurs charges.
- Comptage à chaque cycle, AVANT ecoProgress() : la valeur publiée inclut l'intervalle écoulé,
et l'intervalle qui enjambe l'échéance va à la période close (base gardée à la bascule).
- Aucune décision ne lit purchasedWh : consignes inchangées. update() inchangé.
- INTERFACE.md : contrat réécrit (« publié dans TOUS les régimes » retiré).
- Recorder `decisions` : purchasedWh absent sans mesure, comme la RPC.
- Tests : testPurchasedIsCountedOnTheMeasuredEnergy (simulation, cas i-vi) et
testThePlanOfAnOptimizerBuysNothingEco (socketscheduler, cas vii).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changement VOULU, encadré avant régénération :
1. Moteur e6bc32d + tests de a280820, comparés à l'ancienne référence par
etm-powersync-compare-hors-motifs.py : IDENTIQUE HORS MOTIFS, 23 739
lignes, 23 724 identiques à l'octet, 15 qui ne diffèrent que par le
motif publié (toutes des plans directs du scheduler : SURPLUS_INSUFFICIENT
→ ECO_FLOOR_GRID, et → ECO_FLOOR_MISSED au cycle de clôture). Aucune
consigne, powerW, état, commutation, budget ni soutirage ne change.
2. Moteur et tests de HEAD : identiques à 1. ligne pour ligne hors des deux
fonctions touchées ; testEcoFloorMissedSaysWhatWasLostAndWhy garde ses
7 cycles en préfixe et en ajoute 13, testTheDecisionDescribesTheMergedAction
ajoute 19 cycles. 67 fonctions, 23 705 cycles.
3. check sur la nouvelle référence : IDENTIQUE (23 772 lignes), 0 rejeu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
etm-powersync-compare-hors-motifs.py retire de chaque ligne de la
référence le seul motif publié d'une charge (telemetry.loads[].decision,
plan.timeSlots[].actions[].reason) et le manque chiffré d'une clôture non
tenue (missingWh, closedMissingWh), puis exige l'égalité de tout le reste :
consignes, powerW, états, relais, courants, budget, draw, counts, niveaux.
Il liste les cas qui changent (fonction, charge, ancien → nouveau motif).
Sert de preuve aux correctifs a et b (etm70) avant de régénérer la référence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deux agents testent désormais en parallèle sur la même machine ; le /tmp
privé n'isolait pas les ports fixes du harnais (4847). Recommandation des
rapports brief 4 T1/T2, déjà appliquée dans le script de preuve hors dépôt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Constaté le 2026-10-16 : sur un code inchangé, la vérification échouait une heure après la
génération de la référence. testTheLearningLogIsFedByTheCycle date ses entrées sur
QDateTime::currentDateTimeUtc() (l'heure courante plus trois jours) : la référence ne valait
que dans l'heure UTC où elle avait été produite.
L'enregistreur définit clock_gettime : avec ETM_DECISIONS_EPOCH, CLOCK_REALTIME part de cet
instant au premier appel puis avance au rythme réel ; les horloges monotones (QTimer,
QElapsedTimer) ne sont pas touchées. Le lanceur pose 2026-10-16T05:00:00Z, l'heure où la
référence a été générée : la référence commitée n'est pas régénérée, elle est reproduite à
l'octet (md5 ea2c1db7…, 23 739 lignes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preuve « sans endpoint = identique au bit près », posée AVANT tout refactoring.
`nymea-energy-decisions` est la suite `simulation` compilée à l'identique, plus un
enregistreur branché à l'édition de liens (`--wrap`) : aucune ligne de la suite ni du
moteur n'est modifiée. Chaque cycle (`simulationCallUpdate`) écrit une ligne NDJSON :
télémétrie de l'arbitre (actions du créneau dans l'ordre, motifs, niveaux, budget, soutirage,
consigne posée), actions laissées au proxy, état des Things. Les scénarios qui interrogent
un `RuleBasedScheduler` local voient leur `Plan` sérialisé champ par champ.
Exclus : `command.since` et les compteurs `totalEnergy*` du mock (horloge murale), les
ThingId (alias par rang dans le créneau, à chaque ligne : deux bornes identiques
reçoivent leur rang provisionné dans l'ordre d'un QHash). `planId` n'est pas lu.
Référence générée sur 83483cc (1.15.2+etm69) : 66 fonctions, 23 673 cycles + 36 plans
directs, deux exécutions identiques à l'octet. Chaque processus a un /tmp privé : libnymea
range l'état de test sous /tmp/nymea-test, partagé et cumulatif (348 bornes orphelines
trouvées dans energy-load-configuration.json).
tests/auto/decisions/run-decisions.sh <build> check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>