Skip to content

livex-v1.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 31 Oct 05:11
· 1 commit to main since this release
196ad07

[1.0.0] - 2026-10-30

Changed

  • Site vitrine - SYNE sans visuel de capture, schéma conceptuel à la place : SYNE est un moteur, sans interface à capturer ; les emplacements .ph de la page SYNE et la capture du Launcher affichée à sa place sur l'accueil sont remplacés par SyneSchema.astro (SVG en ligne, libellés localisés, adapté aux deux thèmes), légendée comme « illustration conceptuelle, pas une capture ». ECHOS et PRISM gardent leurs captures réelles. Les animations GIF inutiles (site et dépôt) sont supprimées et leurs mentions retirées : les animations éventuelles restent dans le dépôt et sont servies en WebP/MP4.
  • Site vitrine - refonte UX, SEO et accessibilité : en-tête et pied de page refondus (5 entrées de navigation, sous-menus cliquables, menu mobile accessible), recherche Pagefind, sommaires d'articles, fil d'Ariane et JSON-LD, page 404 bilingue, icônes PWA. Les chiffres publiés (versions des composants, état CI, tests, couverture) proviennent désormais de site/src/data/ au lieu d'être écrits en dur dans les pages ; la documentation docs/website/ est alignée sur l'implémentation réelle (revue du 21 octobre 2026, rapport docs/website/REVIEW-2026-10-21.md).
  • Gouvernance - passage de la licence MIT à la GNU GPL v3.0 ou ultérieure : les composants SYNE, ECHOS, Launcher, syne-mock et site vitrine sont désormais distribués sous GPL-3.0-or-later (copyleft). Le projet Unreal PRISM (prism/, dont PRISM-LDK) n'est pas couvert par cette licence et reste soumis aux conditions d'Epic Games. LICENSE contient le texte GPLv3, NOTICE porte le copyright, le périmètre et la déclaration de licence, et THIRD-PARTY-NOTICES.md recense les composants tiers (dont Unreal Engine, les fichiers modèles Epic Games et les polices OFL). Les métadonnées (CITATION.cff, package.json, pyproject.toml, projets .NET) et les pages du site sont alignées ; les contributions sont reçues sous la même licence et les fichiers source couverts portent un en-tête SPDX GPL-3.0-or-later.
  • ECHOS - schéma SQLite v7 (migration decision_traces) : decision_traces.utility devient nullable (BL-09 / S-28 / D-03 / B-10) ; ajout des colonnes held_utility, decision_state, decision_tick. Une absence (decisionState = none) ne porte plus de 0.0 substitué — un zéro observé et une absence de mesure ne se confondent plus. Migration par reconstruction de table, lignes legacy conservées avec decision_state = NULL. SCHEMA_VERSION bumpé "6" → "7". Parcours unitaire et batch (consume) alignés sur le même contrat nullable.
  • SYNE / Launcher / Console - refactor en classes partielles : découpage mécanique (aucun changement de comportement) de CognitionPipeline, SimulationLoop, GroupSystem, ActionExecutor, KnowledgeBuilder, SimulationOptionsValidator, ControlServer, SimulationController et AnalysisWindowViewModel en fichiers partiels par responsabilité (méthodes préservées, comptes vérifiés). Aucune régression : 604 Core + 91 Console + 174 Launcher verts.

Changed

  • Site vitrine - SYNE sans visuel de capture, schéma conceptuel à la place : SYNE est un moteur, sans interface à capturer ; les emplacements .ph de la page SYNE et la capture du Launcher affichée à sa place sur l'accueil sont remplacés par SyneSchema.astro (SVG en ligne, libellés localisés, adapté aux deux thèmes), légendée comme « illustration conceptuelle, pas une capture ». ECHOS et PRISM gardent leurs captures réelles. Les animations GIF inutiles (site et dépôt) sont supprimées et leurs mentions retirées : les animations éventuelles restent dans le dépôt et sont servies en WebP/MP4.
  • Site vitrine - refonte UX, SEO et accessibilité : en-tête et pied de page refondus (5 entrées de navigation, sous-menus cliquables, menu mobile accessible), recherche Pagefind, sommaires d'articles, fil d'Ariane et JSON-LD, page 404 bilingue, icônes PWA. Les chiffres publiés (versions des composants, état CI, tests, couverture) proviennent désormais de site/src/data/ au lieu d'être écrits en dur dans les pages ; la documentation docs/website/ est alignée sur l'implémentation réelle (revue du 21 octobre 2026, rapport docs/website/REVIEW-2026-10-21.md).
  • Gouvernance - passage de la licence MIT à la GNU GPL v3.0 ou ultérieure : les composants SYNE, ECHOS, Launcher, syne-mock et site vitrine sont désormais distribués sous GPL-3.0-or-later (copyleft). Le projet Unreal PRISM (prism/, dont PRISM-LDK) n'est pas couvert par cette licence et reste soumis aux conditions d'Epic Games. LICENSE contient le texte GPLv3, NOTICE porte le copyright, le périmètre et la déclaration de licence, et THIRD-PARTY-NOTICES.md recense les composants tiers (dont Unreal Engine, les fichiers modèles Epic Games et les polices OFL). Les métadonnées (CITATION.cff, package.json, pyproject.toml, projets .NET) et les pages du site sont alignées ; les contributions sont reçues sous la même licence et les fichiers source couverts portent un en-tête SPDX GPL-3.0-or-later.
  • ECHOS - schéma SQLite v7 (migration decision_traces) : decision_traces.utility devient nullable (BL-09 / S-28 / D-03 / B-10) ; ajout des colonnes held_utility, decision_state, decision_tick. Une absence (decisionState = none) ne porte plus de 0.0 substitué — un zéro observé et une absence de mesure ne se confondent plus. Migration par reconstruction de table, lignes legacy conservées avec decision_state = NULL. SCHEMA_VERSION bumpé "6" → "7". Parcours unitaire et batch (consume) alignés sur le même contrat nullable.
  • SYNE / Launcher / Console - refactor en classes partielles : découpage mécanique (aucun changement de comportement) de CognitionPipeline, SimulationLoop, GroupSystem, ActionExecutor, KnowledgeBuilder, SimulationOptionsValidator, ControlServer, SimulationController et AnalysisWindowViewModel en fichiers partiels par responsabilité (méthodes préservées, comptes vérifiés). Aucune régression : 604 Core + 91 Console + 174 Launcher verts.

Added

  • Site vitrine - recherche, pages d'état et CI performance : index de recherche Pagefind produit au build (postbuild - 79 pages indexées, 2 langues) et recherche modale accessible (ouverture au clavier Ctrl/Cmd+K, piège de focus, Échap, restitution du focus) ; nouvelles pages getting-started/ (démarrage en 5 minutes), etat-du-projet/ (relevé factuel daté), monographie/ (portail des chapitres) et a-propos/auteur/, en français et en anglais ; sommaire automatique des articles ; workflow GitHub Actions lighthouse.yml avec site/lighthouserc.json (performance ≥ 0,95 en erreur, accessibilité/bonnes pratiques/SEO ≥ 0,95 en avertissement) et script npm run check:links (liens internes du build).
  • Vérification syne-mock ↔ SYNE — harnais de parité de la description du monde : scripts/parity-mock-syne-world.js lance le moteur réel en supervisé (profil prism, surcouche world.obstacles/world.obstacleLayout) et le mock en mémoire dans la même géométrie, capture la trame world_initialized des deux sources et compare le jeu de clés, l'en-tête, cells[] champ par champ et obstacles[] ; sortie en code 1 à la première divergence, et refus de conclure si le layout ne bloque aucune case. Toute affirmation d'alignement du mock devient vérifiable au lieu d'être relue : docs/prism/BLUEPRINT_ROADMAP.md §13 porte désormais la ligne correspondante, et syne-mock/docs/CONTRACT_REFERENCE.md la cite à la place de son ancien tableau de terrains. Résultat de référence (15/10/2026) : 4 900 cases identiques, 11 bloquées, 0 divergence.
  • SYNE/LAUNCHER — scénario de campagne prism de bout en bout (complément ADR-017) : --simulation accepte prism en plus de reference ; le profil s'applique sous la surcouche du Launcher (Program.ResolveOptions, priorité §5) et le mode supervisé transporte la configuration résolue (le prepare rejoue désormais le profil complet, pas le seul fichier de surcouche) ; côté Launcher, le champ « Scénario » accepte prism (WellKnownSimulations.Prism, message de refus et validation CanCreateCampaign alignés). Parcours vérifié par un batch supervisé réel avec les arguments exacts du Launcher (world_initialized 1.1 portant simulatedSecondsPerTick: 5/metersPerUnit: 1 + snapshots tick × 5 s) et par un run CLI --simulation prism (provenance result.json : "simulation": "prism"). Suites : SYNE 634 verts (548 Core + 86 Console), Launcher 183 verts (163 unit + 20 intégration), 0 avertissement.
  • SYNE/PRISM — ADR-017, échelle temporelle configurable (spec « Profil gameplay PRISM ») : nouvelle clé simulation.simulatedSecondsPerTick (entier [1, 3600], défaut 60 — 1 tick = 1 minute simulée, comportement historique bit-à-bit inchangé) + clé informative world.metersPerUnit (> 0, défaut 1,0) ; horloge instanciée SimulationClock (remplace la constante statique SimulationTime.TicksPerSimulationMinute), conversion A/B/C appliquée une fois au démarrage (TemporalScale : taux × dt, décroissances ^ dt, périodes / dt ; cadences et effets par action inchangés) ; profil prism (SimulationProfiles.Prism() + configs/simulation/prism.json : monde 2 240 × 2 240, cellule 32, 6 TPS, 5 s/tick, R = 30). Contrats additifs (MINOR) : observabilité 0.3.0 → 0.4.0 (simulatedTimeSeconds dans le snapshot, minutes entières conservées), description world_initialized 1.0 → 1.1 (simulatedSecondsPerTick, metersPerUnit), /api/control/status + simulatedSecondsPerTick. Inventaire complet des quantités par tick (classes A/B/C, preuves fichier:ligne) : docs/syne/TICK_QUANTITIES.md (PR 0) ; ADR-017, ADR-005 précisée, CONFIGURATION.md §6.12, API_CONTRACTS.md et TRANSPORT_API.md mis à jour. syne-mock aligné sur le contrat (temps tick × simulatedSecondsPerTick, plancher des minutes, version description 1.1, profil prism 4 900 cases). Non-régression : 626 tests SYNE verts dont les goldens dorés (543 Core + 83 Console), 48 tests mock, ECHOS inchangé (pytest exit 0).
  • LAUNCHER — deux tests de non-régression des correctifs Windows du Lot W : AppHostLaunchTests lance réellement l'apphost livex-launcher(.exe) publié avec une racine LIVEX_HOME hermétique et exige les huit contrôles de --check (le défaut de manifeste restait invisible car aucune suite ne lançait l'apphost — dotnet test n'invoque que la DLL) ; SessionFileJournalTests relit le journal partagé (ReadSharedLines / ReadSharedTextAsync) pendant que l'écrivain le tient ouvert, et prouve qu'un lecteur naïf (File.ReadAllText) échoue bien sous Windows — c'est exactement le comportement qui motivait la lecture partagée. Suite Launcher portée à 214 tests (162 unit + 20 intégration + 32 E2E).
  • LAUNCHER/SYNE/ECHOS — Lot W : validation Windows de bout en bout (étapes 4 et 5) : machine DESKTOP-MQQ150J (Windows 11 10.0.26200, dotnet 10.0.401 / SDK .NET 10.0.12, Node 25.9.0, Python 3.12.10, dépôt D:\LIVEX). Résultat : build --warnaserror 0 avertissement / 0 erreur, Launcher 162 unit + 15 intégration + 31 E2E = 208 tests, 0 échec, aucun processus orphelin après les trois suites. E2E contre SYNE publié win-x64 + ECHOS réel (venv .venv\Scripts\python.exe, echos-launcher.cmd) : campagne → interruption → reprise → analyse → paquet .livexp scellé et relu. Validations ciblées : ManifestDetector (chemins Windows), ProcessTreeKiller (Job Object), ProcessRunExecutor, arrêt authentifié (24/24 tests filtrés), --check / EnvironmentChecker (8 contrôles, code 0 avec SYNE installé via LIVEX_HOME, code 2 sans composant) et lancement natif de livex-launcher.exe. Constat CI fait le 08/10/2026 : PR #527 vers develop, 13/13 jobs verts (Linux + windows-latest, run 37843436072). Les 4 jobs Windows — jamais exécutés avant, le workflow ne se déclenchant que sur PR vers develop/main — ont révélé et fait corriger quatre défauts : un glob shell non étendu par PowerShell (npm test), un saut dynamique xunit non converti en « ignoré », un verrou mortel CloseAsync/ReceiveAsync dans le serveur d'observabilité SYNE et un plancher de débit mesuré pendant que les deux assemblées de tests tournaient en parallèle.
  • LAUNCHER/SYNE/ECHOS — support Windows, rédaction Linux (étape 5) : les trois manifestes déclarent désormais la clé executable.windows — syne/component.json vers la publication win-x64 (…/win-x64/publish/Simulation.Console.exe, produite en CI par dotnet publish -r win-x64), echos/component.json vers le nouveau echos/echos-launcher.cmd (symétrique du echos-launcher POSIX : venv prioritaire, python en repli), syne-mock/component.json vers src/cli.js. Validation : validate_manifests.py impose la forme path ou les deux clés linux + windows (job « Validation des manifests »). Portabilité Launcher : ProcessManager exécute les .cmd/.bat par cmd.exe /c (CreateProcess ne lance que des .exe), PublishedSyneInstaller lit la clé d'exécutable de la plateforme courante, le banc J3 accepte Windows (venv .venv\Scripts\python.exe, copies des deux lanceurs, liens de répertoire en junction), EarlyExit fabrique son exit-now.cmd. CI : 4 nouveaux jobs windows-latest — syne-dotnet-windows (build + tests), echos-python-windows (lint + pytest), syne-mock-node-windows (npm), launcher-dotnet-windows (unit + intégration + E2E avec SYNE publié win-x64 et ECHOS réel). Vérifié sur Linux : manifestes validés, build --warnaserror vert, Launcher 162 + 15 + 31 = 208 tests, 0 échec, contrat ECHOS vert, flake8 OK — les jobs Windows et le Lot W (comportements natifs) restent à constater (machine Windows §8).
  • SYNE/ECHOS — campagne V3 : cadence, backpressure, lag et reprise worker mesurés (étape 6) : outillage rejouable scripts/v3_campaign.py (SYNE --serve réel + worker echos.dev_ingest réel, run de 30 s en 3 phases de 10 s — nominal / SIGSTOP du worker / SIGCONT, échantillonnage 1 Hz du tick moteur et du tick max en base) ; deux passes à cadence demandée 100 puis 50 t/s, artefacts docs/campaign-runs/v3/results-{100,50}tps.json (hors git). Résultats : cadence 76,4 t/s (0,76×) à 100 demandés et 44,4 t/s (0,89×) à 50 ; le gel du consommateur fige la base au tick exact de l'arrêt (plateaux 573 / 460) puis le rattrapage par lots mesure ≈ 155 t/s (retard final 19 ticks à 100 t/s) ; lag médian 2 ticks à 50 t/s nominal contre ≈ 100 (débit soutenu d'écriture ≈ 57 t/s) ; 1 trou à 100 t/s (client retiré) / 0 à 50 t/s — signalés, jamais masqués. Reprise du worker après mort violente du flux automatisée : test_worker_reconnects_after_violent_syne_death (3/3, constat 0,01 s, reconnexion 0,55–0,60 s, second run réingéré). Parcours UI (états vides, erreurs, verrouillages) : 66 tests ViewModel launcher/Launcher.Tests.Unit/Presentation/. Rapport complet : RAPPORT-ELEMENTS-OUVERTS.md §5.3 (document retiré du dépôt) ; étape 6 bouclée.
  • LAUNCHER — parcours J3 réel de bout en bout (étape 4) : J3RealComponentsEndToEndTests (2 tests E2E) exécute une campagne contre SYNE publié et ECHOS réel (installation hermétique : manifeste et echos-launcher copiés, code et venv liés, ECHOS_ANALYTICS_DB dédié) — interruption après le run 1, paquet Recoverable, reprise sans rejouer RUN-0001 (entrée identique octet à octet, une seule occurrence run_completed, journal « jamais rejoué(s) »), analyses individuelles produites par l'API réelle, /api/runs porte les 3 identités composites, rapport archivé octet à octet identique à une régénération POST /analysis/report, empreintes vérifiées et frontières du paquet contrôlées (aucune entrée hors runs/RUN-xxxx/). Second test : ECHOS arrêté entre deux runs → le second run termine, les données SYNE restent intactes, l'analyse est marquée « indisponible » et l'absence de rapport est consignée, jamais approximée. Nouveau IAnalysisService.AnalysisComponentVersion : la version d'ECHOS qui a produit le rapport est consignée dans le manifeste du paquet (exposée par EchosAnalysisService depuis l'instance qui a répondu, enregistrée par CampaignRunner). CI Linux : le job Launcher crée le venv ECHOS (.github/workflows/ci.yml) pour que les tests J3 s'exécutent au lieu de sauter. Suite Launcher : 208 tests (162 unit + 15 intégration + 31 E2E), 0 échec.
  • SYNE — benchmarks V5 refaits sur la machine de référence V0.1 (étape 7) : machine figée (i7-8750H 6c/12t, 14 Gio, Ubuntu x86-64, .NET 10.0.401 — arbitrage A3), deux passes --benchmark reproductibles (checksums FNV-1a identiques, débits à ±3 %). Premières mesures honnêtes à charge complète (les chiffres ph9 mesuraient des populations mourantes) : 50 → 1354–2257 t/s (cible 30 ✓), 500 → 42,8–53,9 (cible 20 ✓), 1000 → 5,9–6,5 (cible 10 ✗, écart ~1,7×) — goulot : passe de perception, puis événements. Écart documenté dans PERFORMANCE.md §9.2, hors blocage V0.1 (scénarios ≤ 100 agents).
  • SYNE — campagne V2′ par le chemin reset (étape 2) : POST /api/control/reset accepte désormais une surcouche config (JSON partiel, même règle que start/prepare, contrat API_CONTRACTS.md §3 aligné), permettant d'enchaîner des runs de populations différentes par reset ; outillage rejouable scripts/reset_campaign.py (session --serve unique, flux WS segmenté par runId, critères ADR-016). Résultat du 08/10/2026 : 6/6 ✓ (50 et 100 agents × 2500 ticks × 3 seeds enchaînés start → reset ×5) — 0 extinction, 0 mort, |pente| ≤ 0,0037/tick, population = initiale, pentes identiques à celles du chemin prepare (ADR-016). Preuves : ControlServerWireTests.Reset_WithConfig_BuildsTheNewRunWithThatSurcouche, RAPPORT-ELEMENTS-OUVERTS.md §5.1 bis (document retiré du dépôt).
  • SYNE — calibration B1 des défauts intégrés pour la stabilité 2500 ticks (ADR-016) : les défauts du moteur (ConfigLoader.LoadDefaults / SimulationOptions) re-calibrés pour que le chemin réel des runs (Launcher → overlay agents.initialCount + défauts) tienne 2500 ticks — c'est le réglage par défaut qui devient viable, pas seulement un profil : réserves food/water 20 000 (régén 20/10) avec degradationTick null (l'ancienne dégradation à 100 ticks annulait la régénération en fin de période et figeait la réserve), moveEnergyCost 0,05 → 0,03, restEnergyGain 1,5 / restFatigueRecovery 2,0, catalog.eat/drink.energyRecovery 2,0/1,0, dérives sociales socialDriftRate 0,001 → 0,0002 et curiosityDriftRate 0,002 → 0,0005 (besoins sans mécanisme de satisfaction : la pente d'énergie chutait de −0,037/tick), communication bornée (maxSendsPerTick/maxReceivesPerTick = 1, relayEnabled false) et les 4 coûts énergétiques de messages à 0 (l'envoi coûtait 4× le déplacement). SimulationProfiles.Reference() et configs/simulation/reference.json rejouent explicitement les mêmes valeurs — un seul jeu à maintenir. engineVersion 0.14.0 → 0.15.0 (bump MINOR, trajectoire altérée) ; 4 checksums dorés re-pinnés (perception 0xf4aaa2733e491935, état complet 0x954b8e0e70c0af5f — DETERMINISM.md §7) ; 27 tests unitaires adaptés. Validation : campagne défauts purs 50 et 100 agents × 2500 ticks × 3 seeds (12345/424242/999) — 6/6 runs : 0 extinction, 0 mort, |pente| ≤ 0,0037/tick < 0,005, population finale = initiale (contre extinction à t303 avant) ; outil rejouable scripts/calibration_campaign.py. Docs : ADR-016, CONFIGURATION.md §B1, PERFORMANCE.md (note), INSTALLATION.md, VERSIONING.md.
  • LAUNCHER — réalisation des jalons G5 → G7 (contre le banc de stubs ; portes externes P1 – P5 ouvertes) : G5 session complète — service d'analyse HTTP conforme à INTEGRATION_CONTRACT.md §10.1 (AnalyzeRun/AnalyzeExperiment/GenerateReport sur le port de contrôle, joignabilité instance → manifeste → 5000), Stub.Echos doté des trois endpoints d'analyse déterministes et d'un journal de demandes prouvant qu'aucune interface web n'est ouverte pendant la campagne ; rapport archivé identique octet à octet à la réponse d'ECHOS puis affiché tel quel, et un analyste arrêté n'empêche ni la campagne suivante ni son scellement. G6 --check complet selon PACKAGING.md §6 : les huit vérifications du tableau, causes exactes, sortie textuelle stable — l'installation multi-plateformes et la cible 72 h restent des conditions physiques. G7 déverrouillage Immersion évalué : exigences §11.1 (mode porteur, snapshotStream, renderCadence, contrôle) lues au manifeste PRISM avec cause exacte et jalon affichés (carte PRISM, résolution de profil, vue Configuration) ; arguments de démarrage génériques (args communs §3.1 pour tous, args de lot SYNE réservés au moteur) ; Stub.Prism livré (endpoints §6 + client WebSocket factice) ; profil sélectionnable dans Configuration (statut dynamique, démarrage dans l'ordre de résolution, arrêt en inverse strict). Supervision câblée : la boucle de sondes fait réellement transiter les états (Démarrage → Prêt, perte de contact après trois sondes, health.path/intervalMs/startupMs du manifeste honorés) ; stubs en code 3 sur port occupé, ports libérés même en cas d'échec de run. Compatibilité auditée avec les composants réels codés (syne, echos, syne-mock) : écarts component.json, args communs et endpoints standard consignés en docs/launcher/ISSUES.md (O-38 à O-41). Bancs : 80 tests verts (63 unitaires, 7 intégration, 10 bout en bout).
  • LAUNCHER — réalisation des jalons G1 → G4 : première implémentation du Launcher, application .NET 10 / Avalonia dans launcher/, conformément à docs/launcher et aux six ADR. G1 : format de paquet .livexp (Launcher.Package) — ZIP64 vivant puis scellé, ordre d'écriture normatif §5.2, déterminisme octet pour octet vérifié par test, contre-mesures de sécurité (traversal, exécutables, zip-bomb, schéma inconnu refusé). G2 : orchestration sans interface (Launcher.Domain + Launcher.Infrastructure) — machine à états et règles d'agrégation, registre de services, résolution de profils avec verrou Immersion évalué, allocation de ports avec pré-vol et priorité au manifeste, gestion de processus (arrêt propre HTTP + jeton, confinement Job Object / groupe de processus Unix), détection par manifestes, journal de session JSON Lines. G3 : campagnes (Launcher.Application) — exécution séquentielle (ADR-005), graines dérivées, politiques d'échec stop/continue/retry/tolerate, reprise exacte par runs/index.json, annulation, scellement. G4 : interface squelette (Launcher.Presentation + Launcher.App) — navigation à trois modes de poids égal, colonne d'état permanente, dialogue du mode verrouillé, CLI --check, garde d'instance unique. Bancs de composants simulés Stub.Syne/Stub.Echos (INTEGRATION_CONTRACT §13, pannes injectables). Trois bancs de tests verts : 50 unitaires (dont les 10 propriétés du format, l'analyse statique des frontières d'assembly et la navigation à sept entrées), 7 intégration (processus réels et surface HTTP du Launcher), 3 bout en bout (campagne complète contre le stub détecté par manifeste). Audit de conformité doc↔code bouclé : analyses individuelles par run, journaux d'échec archivés, corrélation argument+environnement, provenance.json à la création, run.json enrichi, versions des composants au manifeste (EXPERIMENTS.md §12) ; surface HTTP d'observabilité du Launcher (/health/live|ready|details, /info, /metrics, /registry — NETWORK.md §4.1, OBSERVABILITY.md §4) allouée dans la plage interne 5200–5399. Interface réalignée pixel sur la maquette GUI.md : sidebar à sept entrées, hero débordant, cartes COMPOSANTS avec cycle de vie réel (démarrer/arrêter via /control/shutdown, jeton en variable d'environnement), tableau des expériences depuis les paquets réels, colonne d'état avec ressources échantillonnées (RAM, CPU, stockage) et journal de session ; les trois modes deviennent le sélecteur de type de session de la topbar (réconciliation USER_INTERFACE §2.2). Job CI Build & tests (Launcher .NET).
  • Plan campagne-runs — axes A/B/C/D réalisés (J-C1 → J-C4) : réalisation complète du plan de correctifs campagne-runs du 29/09/2026 (décisions D1/D2/D3 ; document exécuté puis retiré du dépôt — résultats consolidés dans RAPPORT-ELEMENTS-OUVERTS.md §5.1, également retiré du dépôt). Détail par axe ci-dessous (Added/Fixed) ; traces contractuelles dans docs/syne/API_CONTRACTS.md, docs/echos/API_REST.md, EXPERIMENT_COMPARISON.md, CAUSAL_ANALYSIS.md, CONFIGURATION.md et ADR-015.
  • SYNE — calibration de survie du scénario de référence (D1, ADR-015, J-C3) : profil de référence recalé — catalog.eat.energyRecovery = 2.0, catalog.drink.energyRecovery = 1.0 (SimulationProfiles.Reference() + configs/simulation/reference.json) et bénéfice Eat/Drink déplafonné (min(need,100) × 0.6, monotone jusqu'à 60) dans UtilityEvaluator.BenefitOf. Motif : mort lente (énergie moyenne 69 → 49 entre t800 et t1200) et Eat/Drink écrasés par Socialize/Explore (utilité 13,9 vs 83,7). engineVersion 0.12.0 → 0.13.0 (bump MINOR, altération volontaire de trajectoire) ; checksums dorés re-calés et assumés dans le même commit (perception 0x46769cfb11c8b3a7, état complet 0xe62395429b50b7c1 — DETERMINISM.md §7).
  • ECHOS — signaux de viabilité observe-only (A3 + B2, J-C2) : rapport de calibration schemaVersion 2 — outcome (extinct/surviving) et extinctionTick (premier tick où alive_count = 0) + bloc viability (energySlopePerTick sur les 200 derniers ticks, actionSharesWhenHungry quand mean_hunger > 70, resourceRegime food/water). Le résultat de population est aussi calculé en SQL (AnalyticsStore.population_outcome) et exposé par /api/runs et /api/runs/{id} (outcome, extinction_tick) — l'observateur drappe « run fini avec écosystème mort » sans recalcul. Colonnes tick_summaries.mean_food/mean_water (schéma SQLite v6, migration additive, régime vide sur lignes legacy).
  • ECHOS — /api/compare?light=1 (C3, J-C4) : résumé seul (seed/version/score/diffs), sans series ni calcul d'empreinte bit-à-bit — bit_identical/is_reproducible valent null (le verdict exige l'empreinte). L'UI l'utilise par défaut ; l'appel complet reste la référence pour la validation.
  • ECHOS — cadence du contexte agents (C2, J-C4) : contexte persisté 1 tick sur context_every (défaut 20, env ECHOS_CONTEXT_EVERY, min 1 = comportement historique) plus le dernier tick du flux — ~208 → ~12 Ko/tick de contexte, dividende direct pour /compare. Les contextes groups/phenomena/profiling suivent analysis_every (inchangé) ; latest_context/context_before//beliefs//relationships lisent « le plus récent disponible ».
  • ECHOS — détection des trous de ticks (D3, J-C4) : le worker journalise tout discontinuité (dernier tick vu + 1 ≠ tick reçu — reconnexion, reset inter-run) via une ligne structurée et le compteur ConsumeResult.gaps_detected, imprimé par dev_ingest. Le rattrapage (replay SYNE) reste hors périmètre V0.1 — signalé, pas masqué.
  • UI — badge d'extinction (D4, J-C4) : la liste des runs (ControlScreen) consomme outcome/extinction_tick et affiche « éteint à tN » (ou « vivant ») sans recalcul côté client.
  • SYNE — champ seed dans le snapshot (A1, J-C1) : contrat d'observabilité 0.2.0 → 0.2.1 (champ additif) — ECHOS n'a plus à dériver le seed du run_id (dérivation qui produisait seed: "" sur les 3 runs pilotés et invalidait same_seed dans /api/compare). Parité syne-mock (le mock émet seed dans son snapshot).
  • ECHOS — application de bureau (Electron) : shell echos/echos-desktop/ (ADR-003) packagé en .deb (Linux) et NSIS .exe (Windows). Backend Python empaqueté par PyInstaller (onedir) lancé comme processus enfant, interface echos-ui servie par FastAPI sur la même origine (aucun CORS), attente de /health avant ouverture de la fenêtre. Workflow CI echos-desktop.yml (matrice ubuntu/windows, artefacts attachés sur tag echos-v*). Voir docs/echos/adr/ADR-003-shell-electron-desktop.md.
  • Portail documentaire docs/README.md : sépare les documents de référence courants des archives (prototype, Monographie), et pose la hiérarchie des sources de vérité — en cas de divergence, la documentation détaillée et l'implémentation testée du composant priment.
  • Jalon U8 — Constructions = obstacles statiques (SYNE-071) :
    • world.obstacles réactivé (défaut false) + layout initial world.obstacleLayout[] ({id, x, y, radius}) posé au build des mondes (CLI + serveur de contrôle) ;
    • mutation dynamique validée : AddObstacle borné/id unique avec révision, constructions tracées PlaceConstruction/RemoveConstruction (modification d'environnement) ;
    • grille A* re-rasterisable : AStarPathfinder.Refresh() purge le cache LRU et re-rasterise si la révision du monde a changé (no-op sinon), câblé en tête du déplacement déterministe ;
    • traçabilité : événements world.construction_placed/world.construction_removed + champ obstacles[] dans le snapshot (API_CONTRACTS §2) ; bornes world.obstacleLayout[] validées ;
    • engineVersion 0.7.0 → 0.8.0 ; checksums dorés inchangés (0 tirage PRNG, Refresh no-op sur le scénario de référence) ;
    • tests : +20 (8 World, 4 A*, 2 ConfigLoader, 3 Validator, 3 ObservabilitySensor, 2 Console end-to-end) — 345 Core + 17 Console = 362 tests au total.
  • Jalon U8 — Cycle des ressources (SYNE-070) :
    • 4ᵉ type de réserve Mineral (ResourceKind.Mineral, défaut 0) — initialisation enum-driven, exposé dans l'observabilité resources[] (4 types) et persisté SQLite (resources/resource_snapshots) ;
    • régénération & dégradation périodique : ResourceStocks.ApplyLifecycle(tick, settings) appliquée en fin de tick (ordre causal strict, 0 tirage PRNG) — + regenerationRate/tick, dégradation − regenerationRate × degradationTick à chaque période, clamp ≥ 0 ;
    • bornes resources.*.{initial,regenerationRate,degradationTick} ajoutées à la validation (CONFIGURATION.md §6.7) ;
    • engineVersion 0.6.0 → 0.7.0 ; checksums dorés inchangés (pin contractuel, DETERMINISM.md §7) ;
    • tests : +7 Core (cycle, minéral, validation, persistance 4 réserves) — 327 Core + 15 Console = 342 tests au total.
  • Jalon U8 — Persistance SQLite bit-à-bit (SYNE-110/111/112) :
    • SimulationSnapshot/SimulationSnapshotCodec : capture déterministe (JSON System.Text.Json) de l'état complet du monde + cognition + PRNG 4×64, avec hash stable ;
    • SimulationSnapshotRestorer : reprise exacte au tick N sans re-jouage (kill states dérivés reconstruits déterministiquement, holdover LastDecision + réserves restaurées en place) ;
    • SqlitePersistenceStore : schéma V2.0 des 11 tables (PERSISTENCE.md §3, PRAGMA user_version = 2), transactions atomiques, rotation maxBackups (défaut 5), reprise au dernier tick_states (SYNE-112 post-crash) ;
    • Hook d'autosave câblé dans SimulationLoop.AdvanceOneTick (autoSaveEveryNTicks, défaut 1000) — purement en écriture, aucun tirage du PRNG ⇒ checksums épinglés inchangés ;
    • 5 PersistenceTests (315 → 320 tests Core, 329 au total avec Console : reprise bit-à-bit identique au run ininterrompu, état RNG, reprise post-crash, schéma 11 tables, rotation).
  • Jalon U8 — Serveur de contrôle HTTP :5181 (SYNE-113) :
    • ControlServer (HttpListener BCL, zéro dépendance ADR-002/003) + SimulationController (machine à états idle→running⇋paused→finished, gate ManualResetEventSlim + CancellationToken par run) dans Simulation.Console/Control/, activés par --serve (--serve-port, défaut 5181) ;
    • routes POST /api/control/start {seed?, config?} / pause / resume / reset {seed?, runId?} + GET /status (contrat API_CONTRACTS §3) — réponses {ok, action, runId, state, tick, aliveCount, seed} ;
    • non-intrusif (SYNE-081/DETERMINISM §3) : 0 tirage PRNG ajouté ⇒ run piloté == run ininterrompu, vérifié bit-à-bit ; finalise l'interop ECHOS-085 (testé contre le vrai ControlClient ECHOS) ;
    • 6 ControlServerWireTests (320 Core + 15 Console = 335 tests au total).
  • Job CI Intégration mock → ECHOS (ci.yml) : echos/echos/tests/test_syne_mock_integration.py lance syne-mock (Node) et vérifie le trajet complet ControlClient/WsClient → storage.consume (SQLite + Parquet) → API REST, sur toute PR touchant syne-mock/ ou echos/ (gate LIVEX_MOCK_E2E=1). C'est le pendant du job Intégration U8 (SYNE → ECHOS), qui reste réservé au moteur .NET : ECHOS devient donc testable sur chaque PR sans build .NET.
  • Job CI Tests (SYNE-MOCK Node) (ci.yml) : syne-mock/ rejoint la détection de composants modifiés (sortie syne_mock) et la suite npm test s'exécute sur le runner à chaque PR touchant le composant, avec timeout-minutes: 5 en garde-fou contre une régression de type fuite de descripteurs. docs/CI_CD.md §2 annonçait déjà ce job.
  • ECHOS — provenance des métriques (measured) : analysis.provenance(snapshot) distingue, par couple (moteur, métrique), une valeur réellement mesurée du repli neutre du moteur faute de fenêtre. Persisté par tick (tick_metrics.measured, schéma SQLite v5, migration additive) et publié par GET /api/runs/{id}/metrics et GET /api/runs/{id}. Sans ce signal, 7 métriques pouvaient rester à 0 sur tout run réel sans que l'interface puisse le voir — c'est ce qui avait permis leur passage inaperçu. Contrat dans docs/echos/API_REST.md §3.2-§3.3 et METRICS_SPEC.md (annexe). Évol adhésion MINOR : champ ajouté, aucun consommateur existant n'est cassé.
  • echos/ECHOS-REVIEW.md : rapport de revue du composant (584 lignes) — bugs critiques, moteurs, analyse causale, API, robustesse, tests, interface echos-ui, et arbitrage explicite des points restés ouverts (CORS = décision de déploiement, StarletteDeprecationWarning = montée de FastAPI, normalisation /100 = à documenter).

Changed

  • SYNE — la phrase qui laissait croire que les obstacles étaient générés à partir de la seed : docs/syne/API_CONTRACTS.md (§2, description du monde) écrite « Le layout est déterministe pour un seed », qu'un lecteur comprend légitimement comme « le relief est généré ou varie avec la seed » — alors que SYNE-071 pose le layout déclaré sans un seul tirage de PRNG, que world.obstacleLayout[] est la seule source des obstacles, et que syne-mock refuse même la clé world.proceduralObstacles pour ne pas publier un monde que le moteur ne produit pas. Reformulé en « indépendant de la seed : deux seeds sur la même configuration donnent le même relief — la seed ne fait varier que les positions initiales des agents, les marqueurs de ressources et la dynamique », avec l'avertissement explicite qu'un changement de seed ne régénère pas les obstacles. docs/syne/CONFIGURATION.md §6.8 dit désormais la même chose au même endroit que les clés : layout déclaré, pas généré, et une variation par seed exigerait une clé de configuration et un générateur qui n'existent pas en V0.1 (donc une décision à prendre, pas un paramètre oublié). Aucun code touché : la doctrine du moteur est inchangée.

  • ci.yml se déclenche sur les PR visant main autant que develop. Sans cela, aucune PR de release ou de hotfix n'aurait produit le moindre check, et la protection « branche à jour + CI verte » de main resterait bloquée indéfiniment en attendant un status qui n'arrive jamais.

  • Dossier de documentation PRISM réécrit pour le projet Unreal final et son plugin PRISM-LDK : world_initialized, snapshot global par tick, deltas/événements et cycle de contrôle prepare/ready/start/pause/resume/stop/reset deviennent la référence du composant ; les instructions d'implémentation Godot sont remplacées par le périmètre réel du plugin, et les spécifications visuelles sont recadrées en objectifs de présentation (elles ne décrivent pas des fonctions déjà livrées).

  • Décision d'architecture actée : PRISM est le projet Unreal final de LIVEX et intègre le plugin Unreal PRISM-LDK (LIVEX Development Kit, module technique PrismLdk). L'ADR-002 formalise ce choix et supersède l'ADR-001 (prototype Godot), désormais conservée comme décision historique. SYNE reste le seul moteur décisionnel et l'autorité de l'état simulé ; prism/LDK/LDK.uproject est un hôte technique de développement/build du plugin, pas un second produit.

  • ADR-003 (API HTTP) et ADR-004 (WebSocket temps réel) précisent désormais que la liste des routes et des formats qu'elles portaient est celle de la proposition d'origine de la Monographie, et renvoient vers docs/syne/API_CONTRACTS.md et docs/COMMUNICATION.md pour le contrat courant (diffusion multi-consommateur, cycle prepare/ready/start/pause/resume/stop/reset).

  • Documentation transverse réalignée sur PRISM, projet Unreal final de LIVEX : README.md, docs/ARCHITECTURE.md (§2.4 syne-mock, §6 arborescence), docs/presentation/VISION.md, docs/governance/ROADMAP.md, docs/presentation/FAQ.md, docs/INSTALLATION.md, docs/COMMUNICATION.md, SECURITY.md, docs/CI_CD.md, CONTRIBUTING.md et la Monographie. Les références à Godot et à godot-renderer/ ne décrivent plus l'implémentation actuelle ; docs/ARCHITECTURE.md devient la source de vérité pour les responsabilités, le plugin PRISM, LDK et les projets Unreal.

  • Portée de syne-mock cadrée explicitement comme outil de développement des contrats Unreal, pas un moteur de simulation : README.md, docs/ARCHITECTURE.md §2.4, CONTRIBUTING.md §4 (checklist npm test --prefix syne-mock), docs/CI_CD.md §2 et §5, docs/presentation/ETHICS_AND_SCOPE.md §4.1 et docs/prism. Ses résultats ne doivent pas être présentés comme ceux de SYNE.

  • docs/echos/{ISSUES,ROADMAP,TESTING}.md : la validation ECHOS-091 ne couvre plus « le runtime PRISM qui commence après U8 » mais est explicitement restreinte à SYNE→ECHOS. La validation du plugin PRISM-LDK et du projet Unreal PRISM relève d'un chantier distinct.

  • docs/docs_prototype/README.md porte désormais un bandeau d'archive pointant vers docs-prism/ et docs/README.md : les références à Godot qu'il contient sont historiques. [répertoire docs/docs_prototype/ retiré depuis par la revue documentaire V0.1, PR #502]

  • CONTRIBUTING.md : ajout de la validation de compilation du plugin PRISM-LDK dans l'hôte Unreal de développement et dans le projet PRISM final.

Fixed

  • Site vitrine - le partage e-mail des articles était retiré : le bouton « E-mail » n'ouvrait aucune adresse (il lançait seulement le client de messagerie du visiteur avec le lien) — il est supprimé ; la copie du lien, X/Twitter, LinkedIn et la feuille de partage native suffisent. Aucun mailto: ne reste publié sur le site.

  • Site vitrine - débordement horizontal du menu mobile et contact direct par formulaire : le bouton « Suivre » du menu mobile héritait de width: 100% et des marges latérales, débordant de 40 px et créant un défilement horizontal dans le panneau ; il est désormais borné à la largeur du menu. Les pages auteur et mentions légales ne publient plus d'adresse e-mail : le contact passe par le formulaire du portfolio (donovan-dev-web.vercel.app/contact).

  • Site vitrine - entités HTML affichées en clair et chaînes françaises en pages anglaises : les attributs Astro (title, text, cta) ne décodent pas les entités HTML - &eacute; et &rarr; y étaient ré-échappés en &amp;eacute; et s'affichaient littéralement (le bloc « Pourquoi LIVEX » de l'accueil affichait « Déterminisme » et « Voir les mesures → »). Toutes les entités hors <code>/<pre> sont désormais écrites en UTF-8 direct ; les pages anglaises utilisent les chaînes localisées (PROJECT_STATE_EN, RELEASE_EN) au lieu des chaînes françaises (pourcentages, état CI, support Windows, date de relevé) ; les trois pages anglaises créées récemment transmettent lang="en" à PageHeader et CardLink (fil d'Ariane, libellés d'accessibilité) ; tirets, apostrophes et libellés harmonisés (tiret cadratin, « Portail des chapitres », « Guide de contribution »).

  • Site vitrine - défauts de fond corrigés : la page 404 renvoyait un document sans h1 et une URL canonique hors du base /LIVEX/ ; les images d'articles étaient servies en PNG brut, sans dimensions ni optimisation ; les tableaux Markdown ne portaient pas scope="col" ; les articles publiés uniquement en français déclaraient un hreflang anglais vers une page qui n'en est pas la traduction ; le JSON-LD WebSite pointait vers la racine du domaine au lieu du base ; le manifeste PWA figeait /LIVEX/ (icônes en 404 sur un build à la racine) ; les liens « Discussions » menaient à des discussions GitHub désactivées (remplacés par SUPPORT.md). Validation : 80 pages construites, astro check sans erreur ni avertissement, 0 lien interne cassé, 207 ancres résolues, 128 URL externes valides, Lighthouse 100/100/100/100 sur six pages.

  • SYNE - les marqueurs de ressources tombaient dans les obstacles que la description publiait pourtant comme bloqués : WorldDescriptionBuilder.BuildResourceLocations piochait la case du LCG local sans regarder la marchabilité, alors que la passe de couverture juste au-dessus remplissait déjà cells[].walkable et movementCost: 100 — PRISM instanciait une ressource dans un rocher et affichait un marqueur sur une case que le même message annonçait infranchissable. Mesuré contre le moteur réel et le mock, profil prism livré, seed 1 : resource-food-2 sur la case (8, 68), dans la côte sud (cote-sud-1, r = 150) — le moteur publiait 11 des 12 marqueurs identiques au mock, le douzième déplacé. Correctif : la couverture des obstacles est calculée une fois par case et partagée par cells[] et par le placement, qui avance jusqu'à la première case libre et minore le nombre d'emplacements par le nombre de cases marchables (0 case libre ⇒ aucun marqueur, au lieu d'une boucle). Aucun tirage de simulation n'est consommé (LCG local, déjà séparé du Xoshiro) : la trajectoire des ticks est inchangée. Preuves : 4 nouveaux tests WorldDescriptionTests, dont le rejeu du cas mesuré (profil prism, seeds 1 à 8 : la case (8, 68) reste bloquée et ne porte aucun marqueur) ; le harnais scripts/parity-mock-syne-world.js compare désormais resources[] champ par champ au lieu de l'exclure — c'est cette exclusion qui laissait la divergence vivre — et accepte --seed pour rejouer une graine précise ; moteur et mock donnent 12/12 marqueurs identiques aux seeds 1 et 42, exit 0.

  • syne-mock — un fichier de configuration introuvable était ignoré en silence : readConfig rendait {} dès que le chemin n'existait pas, donc npm start -- config.prism.json (point au lieu du souligné) démarrait le mock sur les défauts — 500 × 500, 50 agents, obstacles: 0 — pendant que l'opérateur croyait charger le profil prism : le monde publié ne correspondait ni à sa configuration, ni au moteur, et le journal n'en disait rien (constaté sur PREPARE complete (world=500x500 ... agents=50) alors que le fichier annonce 2 240 × 2 240/100 agents). Un chemin demandé est désormais une erreur : message nommant le chemin résolu, code de sortie 1, démarrage refusé ; seul un appel sans chemin garde les défauts. Le message propose en plus le fichier voisin correspondant quand il est unique (séparateurs et casse ignorés), donc l'erreur de frappe se répare d'elle-même — Vouliez-vous dire : …config_prism.json ?. Tests : readConfig d'un chemin absent lève et cite config_prism.json, un chemin sans voisin n'invente aucune suggestion, config_prism.json annonce bien le monde du moteur (2240/32/6 TPS/5 s/tick, 5 disques, 203 cases bloquées) — 51 tests, 49 verts, 0 échec, 2 skip.

  • syne-mock/PRISM — le mock publiait un monde que SYNE ne sait pas produire (0 obstacle, plus de mer/rivières/obstacles procéduraux côté PRISM) : la WorldDescription du mock divergeait du moteur sur trois points mesurés — world.terrain actif par défaut (seaDepthRatio 0,12 + une rivière : 300 cases sea, 44 river, et movementCost: Infinity sérialisé en null), des obstacles à formes (shape/cells) et proceduralObstacles que StaticObstacleSettings { Id, X, Y, Radius } n'a jamais connus. Le mock émet désormais exactement la règle de WorldDescriptionBuilder.Build : terrainType toujours plains (le moteur n'a ni biome ni palette de terrains), walkable vrai si et seulement si la case ne porte aucun obstacle, movementCost 1 ou 100, obstacles en disques {id, x, y, radius}. world.terrain, world.proceduralObstacles et un obstacle non-disque sont refusés au chargement avec un message explicite, au lieu d'être ignorés — c'est cette indifférence aux clés inconnues qui avait laissé la divergence vivre. Sur le transport, /api/world, la réponse de prepare et la trame world_initialized publient la même géométrie (la trame diffusée envoyait auparavant la représentation interne, avec type). Preuve : nouveau scripts/parity-mock-syne-world.js, qui lance le moteur réel (profil prism : 2 240 × 2 240, cellule 32, deux disques r = 8 et r = 40) et le mock sur la même géométrie, puis compare jeu de clés, en-tête, cells[] champ par champ et obstacles[] — 4 900 cases identiques, 11 bloquées de part et d'autre, 0 divergence ; le script sort en code 1 dès la moindre différence et refuse de conclure si le layout ne bloque aucune case (comparer deux mondes vides ne prouverait rien). Côté plugin, ParseWorld ne peut plus arrêter le jeu sur une description incomplète : cells/obstacles/resources/regions sont lus en TryGetArrayField (l'ancien GetArrayField déclenche un checkf moteur quand la clé manque) et un terrain inconnu ou un movementCost sans valeur émettent un avertissement nommant la première valeur fautive, au lieu d'un Unknown silencieux ; l'absence d'obstacles, légitime quand le profil n'en déclare aucun (configs/simulation/prism.json : world.obstacles faux par défaut), n'est plus un avertissement mais une ligne d'information qui nomme la cause probable. Tests du mock : 47 pass / 0 fail / 2 skip (dont un test de la règle de description — aucun null sérialisé, aucun terrain hors plains), les 4 tests de la mer et des rivières remplacés par leurs équivalents obstacles.

  • LAUNCHER — un run réussi devenait Échoué sur son propre journal (défaut de CI n°5, échec de la reprise J3) : ProcessRunExecutor.CollectLogFiles et CampaignRunner.CollectFailureLogsAsync archivaient les journaux d'un run avec File.ReadAllBytes, c'est-à-dire pendant que la pompe détachée du moteur (ProcessManager.PumpAsync) peut encore écrire stdout.log/stderr.log. Sous Windows un lecteur ouvert avec FileShare.Read refuse l'écrivain en cours : l'archivage d'un run pourtant terminé lançait IOException, le run passait Échoué, puis la collecte d'échec retombait sur le même verrou — l'exception de lecture remplaçait la cause réelle et sortait de la campagne (job launcher-dotnet-windows, J3RealComponentsEndToEndTests à l'étape de reprise ; les passages CI précédents de la branche étaient verts, la course ne se joue que sous contention). C'est la règle déjà appliquée au Lot W pour le journal de session (SessionFileJournal.ReadShared*), étendue à l'archivage des journaux de run : nouveau RunLogArchive.Collect (lecture en FileShare.ReadWrite), réutilisé par les deux chemins. Un journal réellement illisible n'échoue plus la collecte : il est consigné dans logs-unreadable.txt avec sa cause — le diagnostic d'un échec ne peut plus être remplacé par l'échec du diagnostic. Preuves : RunLogArchiveTests (3 tests, dont 2 Windows — le lecteur naïf lève bien IOException là où Collect réussit, et un journal verrouillé en exclusif est consigné au lieu de tuer la collecte) ; suite Launcher 214 tests verts (162 unit + 20 intégration + 32 E2E dont J3 contre SYNE publié win-x64 et ECHOS réel), build --warnaserror 0/0 ; PR #527 revalidée 13/13 (run 37850315626).

  • SYNE — verrou mortel du serveur d'observabilité sous Windows (2 tests sur 4 de ObservabilityServerWireTests en échec, testhost bloqué à l'infini) : ObservabilityServer.DisposeAsync — et le retrait d'un client après échec d'envoi — lançait CloseAsync pendant qu'une ReceiveAsync du client était en vol. L'implémentation Windows de System.Net.HttpListener (WebSocketBase.TakeLocks / Monitor.Enter) fait partager un verrou aux deux opérations : le fil de fermeture attendait celui de la réception, la réception annulée attendait celui de la fermeture — délai d'attente mutuelle, qu'aucun des deux ne pouvait lever (le CancellationTokenSource de 2 s n'interrompt pas un Monitor.Enter). Le test restait donc bloqué après ses assertions, au niveau du await using du serveur. Correctif : ordre strict annuler la réception (CTS par session) → attendre la boucle DrainAsync (bornée à 2 s) → fermer ; RemoveClient ne ferme plus jamais pendant qu'une réception tourne. Preuves : classe rejouée 10/10 (≈ 2/10 auparavant), suite SYNE 593 tests verts (79 Console + 514 Core), build --warnaserror 0/0. Note d'enquête : le « Plantage du processus hôte de test » annoncé par vstest était un artefact du timeout d'investigation (exit 143 = SIGTERM), la vraie cause étant le blocage.

  • ECHOS/syne-mock — 12 tests qui n'échouaient que sous Windows : (1) ECHOS — 43 lectures/écritures de fichiers sans encoding= lisaient les fixtures UTF-8 avec l'ANSI cp1252 → 6 comparaisons dorées et instanciations fausses ; désormais encoding="utf-8" explicite partout (sans effet sur Linux, UTF-8 y étant déjà la locale). (2) ECHOS — 3 contrats d'adaptateur purement POSIX : bit d'exécution de echos-launcher (Windows n'a pas de mode de fichier) et exécution directe du script → WinError 193 ; l'adaptateur Windows echos-launcher.cmd est désormais lancé via COMSPEC /c (chemin réel qu'emprunte le Launcher, clé executable.windows) avec nettoyage d'arbre, et le bit d'exécution n'est contrôlé que sur POSIX. (3) syne-mock — 2 tests de fermeture gracieuse SIGTERM/SIGINT inapplicables (Windows livre les signaux par TerminateProcess : code null, signal 'SIGTERM') → ajournés avec motif, le chemin POSIX restant couvert par Linux ; même garde plateforme sur le bit d'exécution de src/cli.js. Preuves : ECHOS flake8 0 + pytest 0 échec (dont intégrations U8 SYNE → ECHOS et mock → ECHOS), syne-mock 44 pass / 0 fail / 2 skip.

  • LAUNCHER — livex-launcher.exe ne démarrait jamais sous Windows (« la configuration côté-à-côté est incorrecte ») : launcher/Launcher.App/app.manifest déclarait <assembly manifestType="dualIdentity"> sans manifestVersion — le chargeur Windows refuse le manifeste (journal SideBySide, événement 11 : « l'élément assembly du manifeste ne contient pas l'attribut manifestVersion correct », puis événement 64 : « l'attribut manifestType n'est pas autorisé dans ce contexte »). Conséquence : l'apphost s'arrêtait au démarrage, quel que soit l'argument, tandis que dotnet livex-launcher.dll fonctionnait — aucune suite ne lançait l'apphost, le défaut était donc invisible en CI. Manifeste réécrit au format canonique (manifestVersion="1.0") ; vérifié en PowerShell : livex-launcher.exe --check s'exécute nativement.

  • LAUNCHER — l'export et l'affichage des journaux de session échouaient sous Windows : OrchestrationFacade.ExportSessionLogsAsync et RecentLogs lisaient le fichier avec File.ReadAllText/File.ReadLines, c'est-à-dire pendant que SessionFileJournal l'a encore ouvert en écriture → violation de partage sur Windows : l'export des journaux tombait en erreur et le panneau « journaux récents » retournait silencieusement la dernière vue (jamais rafraîchie). Lecture partagée mutualisée (SessionFileJournal.ReadSharedLines / ReadSharedTextAsync, FileShare.ReadWrite), réutilisée par le banc J3.

  • LAUNCHER — un arrêt forcé tuait tous les composants vivants et retirait le garde-fou anti-orphelin : ProcessTreeKiller.KillTreeWindows terminait le Job Object unique partagé par toute la session (TerminateJobObject) puis fermait son handle — le job contient tous les composants assignés (donc collateral), et KILL_ON_JOB_CLOSE ne protégeait plus ensuite. Désormais : assignation au démarrage (MakeOwnProcessGroup, symétrique du groupe de processus Unix) et arrêt forcé borné à l'arbre du composant (Kill(entireProcessTree: true)), le job n'étant ni terminé ni fermé avant la sortie du Launcher.

  • LAUNCHER — bancs E2E/intégration rendus valides sous Windows : StubInstall.WaitForHealthyAsync ne interceptait que HttpRequestException alors qu'un port sans écoute ne rend ECONNREFUSED qu'après ≈ 2,05 s sous Windows (retransmission du SYN — « slow TCP connect on Windows », daniel.haxx.se, 14/08/2024), soit après l'expiration des 2 s du client → le démarrage en cours était pris pour un échec ; le manifeste du banc CampaignEndToEndTests omettait la clé executable.windows (« binaire absent ») et ses compositions n'étaient pas libérées (racine non supprimable sous Windows) ; PublishedSyneHarness ne libérait pas son journal ; Sonde_health_ready_repond sondait un stub dont l'horizon (200 ms) était plus court que la latence de refus Windows — horizon porté au-delà du délai de sondage, assertions inchangées.

  • SYNE — un client WebSocket lent figeait le moteur de simulation : SendTextAsync laissait échapper le TimeoutException de WaitAsync(SendTimeout) (absent du filtre du catch) — le client n'était donc jamais retiré et chaque trame rebloquait 2 s : découvert pendant la campagne V3, le moteur tombait à 1,5 t/s au lieu de ~77 dès qu'un consommateur cessait de lire (rétroaction consommateur→moteur, contraire au contrat d'observabilité best-effort). TimeoutException ajoutée au filtre du catch → RemoveClient (fermeture du socket, client abandonné retiré, trame perdue comptée honnêtement comme trou). Test de non-régression ObservabilityServer_ClientThatStopsReading_IsRemovedAndBroadcastKeepsWorking (client qui ne lit plus : la diffusion continue, le client est retiré). Mesures avant/après de la campagne V3 : consommateur gelé 10 s → 71,2 t/s au lieu de 1,5 ; suite SYNE 593/593.

  • SYNE — le chemin reset applique le profil de référence (étape 1) : POST /api/control/reset relançait le run sur ConfigLoader.LoadDefaults() au lieu du profil de référence (ef878f21) — une campagne pilotée longue par reset pouvait redémarrer sur un autre profil que prepare (mesuré le 30/09 : extinction t300 par reset vs survie 1200/1200). PrepareCoreAsync applique désormais SimulationProfiles.ReferenceJson() quand la surcouche est nulle (doctrine ADR-016 : le chemin HTTP ne dépend jamais d'un défaut qu'on pourrait oublier de recaler). Test de non-régression Reset_WithoutConfig_BuildsTheSameReferenceProfileAsPrepare ; SYNE vert (590 tests), oracles inchangés, engineVersion conservée.

  • LAUNCHER — impossible d'archiver une donnée de run au-delà de 2 Gio (« The file is too long ») : trois bornes byte[]/MemoryStream (2 147 483 647 o) bloquaient la chaîne d'archivage d'un vrai stream.jsonl de campagne (mesuré : 2 337 158 145 o pour 2500 ticks × 50 agents, soit ~4,7 Gio à 100 agents) — (1) ProcessRunExecutor.CollectFiles lisait chaque fichier avec File.ReadAllBytes → le run échouait après exécution, le manifeste restait sans runIds et ECHOS refusait le rapport (422) ; (2) LivexPackageWriter.CompleteRun bufferisait chaque entrée dans le MemoryStream du mode Update de ZipArchive ; (3) Seal replaçait toutes les entrées dans un SortedDictionary<string, byte[]>. Correction de bout en bout : nouveau type RunDataFile (source fichier ou octets, jamais de lecture en mémoire), CompleteRun réécrit l'archive en mode Create (compression directe dans le fichier, échange atomique via temporaire, ordre normatif §5.2 et déterminisme inchangés), hachage d'intégrité calculé en flux pendant la copie, Seal relit en mode Read (flux) et réécrit en mode Create, VerifyRunIntegrity hache en flux, ReadEntry documenté réservé aux entrées courtes. Plafond d'entrée du lecteur relevé de 4 à 16 Gio (PACKAGE_FORMAT §9) — les runs 100 agents × 2500 ticks le dépasseraient, le ratio zip-bomb 100:1 reste. Rapport de campagne : le compteur vide runIds est maintenant détecté côté Launcher (avertissement explicite au lieu d'un 422 opaque). Preuve : test de régression > 2 Gio (archive + scellement + vérification) + archivage du vrai stream.jsonl de 2,34 Gio ; 206 tests Launcher verts (15 intégration, 162 unitaires, 29 bout en bout dont campagne SYNE publié réel).

  • LAUNCHER — délai d'analyse ECHOS porté à 30 min pour les opérations lourdes : le client HTTP unique plafonnait à 30 s, alors que l'ingestion d'un flux réel de 2500 ticks × 50 agents (2,34 Gio) prend 5 min 30 s mesurées (~11 min à 100 agents) — l'ingestion était systématiquement coupée, analysis/experiment trouvait « run inconnu » et le rapport de campagne n'arrivait jamais. Le délai est désormais posé par requête : 30 s pour les appels ordinaires (santé, état, 5 s pour l'ingestion live), 30 min pour ingest/run, analysis/run, analysis/experiment et analysis/report (EXPERIMENTS.md §11). Décliné avec la mesure dans docs/launcher/EXPERIMENTS.md §11.

  • SYNE — rejet 413 : le client recevait « broken pipe » au lieu de la réponse : sur un Content-Length déclaré > 1 Mio, ControlServer répondait 413 et fermait la socket pendant que le client écrivait encore son corps — la fermeture avec des octets non lus émet un RST et l'expéditeur (HttpRequestException) ne voyait jamais le 413 (course perdue sous charge parallèle, test OversizedBody_IsRejected_With413_AndNotBuffered échouait en 2 runs sur 3). Vidage borné (16 Mio max, tampon jetable 8 Kio, corps jamais accumulé — la garantie « not buffered » tient) avant d'écrire la réponse ; le 413 part de toute façon si le vidage échoue.

  • SYNE — configs/simulation/reference.json n'appliquait pas la calibration D1 : le fichier portait agents.actions.catalog.eat.energyRecovery au lieu de agents.actions.catalog.entries.eat.energyRecovery — System.Text.Json ignore les propriétés non mappées, donc toute exécution --config configs/simulation/reference.json (dont scripts/batch_analysis.py) tournait avec eat/drink.energyRecovery = 0 (mesuré : energyDelta moyen d'Eat = −0,2 au lieu de +1,8), sans aucun avertissement. Chemin entries corrigé ; le fichier rejoue désormais explicitement les valeurs B1.

  • SYNE — TickBudgetSnapshot.ComputationShare() mélangeait des unités : elle sommait MeanMs(phase) (temps par échantillon) alors que les phases intra-entité sont échantillonnées par entité × tick et les phases réseau/monde par tick — la part computationnelle affichée revenait à (communication + événements) / tick et sous-estimait les phases intra-entité d'un facteur ≈ population. Corrigé en temps par tick (_totalMs / PipelineSamples, ADR-016) ; le seuil ≥ 30 % (PERFORMANCE.md §9) reste tenu.

  • Contrat SYNE↔ECHOS — le run est enregistré avant ses ticks (A2, J-C1) : consume() ne posait record_run qu'au premier snapshot du flux (run_known jamais réinitialisé) — lors d'un reset SYNE, les ticks du nouveau run_id levaient FOREIGN KEY constraint failed (ticks 1–365 et 1–106 perdus en campagne, rapport de calibration du run interrompu jamais écrit). Le pipeline détecte désormais le changement de run_id en flux : nouveau run enregistré avant son premier tick, fenêtres glissantes (_RollingContext) réinitialisées (elles contaminaient les métriques du run suivant), série Parquet flushée, et rapport de calibration bâti à la fin de chaque run — plus seulement pour le dernier run_id vu.

  • Contrat SYNE↔ECHOS — format canonique du run_id piloté (A1, J-C1) : SimulationController.StartAsync générait un run_id aléatoire sans seed (les runs pilotés perdaient leur seed côté ECHOS) alors que le monde préparé utilisait run-<seed> (collisions entre runs successifs de même seed). Format unique run-<seed>-<12hex> : seed lisible (repli de dérivation ECHOS sur les flux ≤ 0.2.0), unicité garantie.

  • SYNE — PrintSummary : ligne « (tête) tick 1 » avec le compteur final (D1, J-C4) : le résumé de fin de run affichait le nombre d'entités final sur la ligne de tête (« tick 1 … 42 entités » après une extinction). Le compte initial est capturé avant loop.Run et passé à PrintSummary ; la ligne de queue reste le compteur courant.

  • SYNE — erreur de start orientée après un run finished (D2, J-C4) : un start sur un run terminé naturellement répondait 409 world_not_ready (orientant vers prepare au lieu de reset). Nouveau code 409 run_finished avec le détail « run terminé — appelez /api/control/reset avant de redémarrer » (API_CONTRACTS.md §3 mis à jour, wire test).

  • ECHOS — performance des routes de lecture (C1, J-C4) : /api/runs/{id} et /api/emergent-phenomena chargeaient tous les contextes (contexts(), dont agents ≈ 208 Ko/tick) pour ne consommer que phenomena — timeouts > 20 s et OOM-kills en campagne. Lecture ciblée observations_for(run_id, "phenomena") (mesuré 0,28 s sur la campagne).

  • ECHOS — revue et refactor du composant (echos/ECHOS-REVIEW.md) : deux bugs qui rendaient des fonctions centrales inopérantes — /api/compare renvoyait 500 sur tout run contenant un événement (la comparaison de reproductibilité n'était donc accessible sur aucun run réel), et AnalyticsStore.runs() plantait sur un run sans tick, ce qui rendait l'API entière inopérante sur une base fraîche ou un run en cours d'acquisition. Sept moteurs refondus : 7 métriques à 0 sur tout run réel (history/communityHistory jamais produits par le pipeline), dénominateur des taux de groupe dépendant de la charge (facteur 50 entre un run calme et un tick chargé), singletons comptés comme communautés, DecisionDiversity calculée sur les objectifs, SystemComplexity non borné, _availability(100, None, 0) renvoyant 1e11, cycle d'import emergence. Robustesse : cadences nulles, fuite de descripteurs, reconnexion à 100 ms, pyarrow importé mais non déclaré (pip install echos produisait un paquet qui ne démarrait pas). Identifiants de groupe (members, leaderId) alignés sur les identifiants d'agent — entiers contre chaînes, donc jamais jointables. Tests : 233 → 283 (couverture 93 %), flake8 propre.

  • ECHOS — interface echos-ui : l'export « CSV » téléchargeait une enveloppe JSON illisible par tout tableur ; beliefs/relationships/phenomena appelaient l'API sans run_id et affichaient donc les données du run le plus récent sous le nom du run sélectionné, sans signal visible ; le cache de métriques ignorait la cadence dans sa clé, fusionnant des séries d'échantillonnages différents et produisant des NaN rendus comme des zéros ; le graphe social relançait une requête /api/relationships par membre en série à chaque tick ; la jauge de vitesse de diffusion alertait en rouge un monde qui propageait bien ; disconnect() se reconnectait 2 s plus tard. npm test lance désormais vitest run (le mode interactif ne termine jamais et accrochait la CI). Tests : 18 → 61 sur 14 fichiers, eslint et tsc propres.

  • syne-mock : ordre des trames WebSocket incompatible avec ECHOS. Le mock diffusait les événements du tick avant son snapshot, alors que SYNE (ObservabilityTickEmitter) et API_CONTRACTS.md §2 imposent le snapshot en tête. ECHOS rejette ce flux : TickAlignmentError: … événement decision_made reçu avant le premier snapshot (ingestion/stream.aligned_ticks). step() regroupe désormais les événements du tick, diffuse snapshot puis tick_summary, puis les événements de ce tick ; l'ordre des événements entre eux et le tampon de diagnostics sont inchangés, stepReplay() n'est pas touché. Contrat, checklist et README du mock alignés sur l'ordre réel, avec la procédure pour brancher ECHOS sur le mock (SYNE_OBSERVABILITY_URL, SYNE_CONTROL_URL) et ce que le mock ne couvre pas.

  • docs/prism/adr/ADR-001-choix-godot.md : la coquille « qui formality ce choix » est corrigée en « qui formalise ce choix ». L'entrée CHANGELOG.md qui annonce cette correction décrivait en réalité la coquille de CHANGELOG.md lui-même ; le fichier ADR visé n'avait jamais été corrigé. Saut de ligne final ajouté à cet ADR et à docs/prism/adr/README.md.

  • CHANGELOG.md : la section ## [Unreleased] cumulait des sous-sections dupliquées (### Changed ×3, ### Added ×2) et une ligne blanche parasite coupant une liste en deux, ce qui cassait le rendu de la section et la comparaison d'un release. Sections regroupées et ordonnées Added → Changed → Fixed → Deprecated, avec correction de la coquille « L'ADR-002 formality ce choix » → « formalise ce choix ».

  • Cadrage Jalon U8 (PR cadrage docs) : planche U8 du ROADMAP racine recalée sur le backlog réel — plage ECHOS 080…093 fictive remplacée par 080…085 (ph8, livrés U7) + 090…092 (ph9) ;

  • Ajout de la carte manquante SYNE-113 (serveur de contrôle HTTP :5181, cible réelle du relais controlClient ECHOS — absente du backlog) ;

  • SYNE-110 précisée : hooks autoSaveEveryNTicks (défaut 1000) / maxBackups (défaut 5) à brancher sur la boucle (aucun consommateur à ce jour) ;

  • ECHOS-085 annotée : livrée côté UI, finalisation en U8 contre SYNE-113.

  • PERSISTENCE.md §3 recalé sur le modèle réel V0.1 : agent_snapshots sans health fictive (besoins = energy, hunger, thirst, fatigue), implémentation du snapshot bit-à-bit et de la rotation documentée.

  • syne-mock : npm test ne se terminait plus. createServer().close() arrêtait l'écoute sans libérer les ressources : les clients WebSocket restaient connectés et les connexions HTTP en keep-alive n'étaient pas fermées, ce qui laissait 2 serveurs TCP et 3 sockets ouverts et empêchait node --test de rendre la main. close() termine désormais les clients WebSocket, purge les connexions HTTP et chaîne les rappels de fermeture ; les tests libèrent leur serveur via t.after() et attendent la fermeture du client.

  • syne-mock : les assertions d'obstacles attendaient un objet {id, x, y, radius} alors que WorldDescription publie aussi type. Les attentes sont alignées sur le contrat réel.

  • syne-mock : les agents initiaux pouvaient apparaître sur une case non franchissable. createInitialAgents ne testait que les obstacles et ignorait le relief, donc un agent pouvait se poser sur une cellule sea (walkable: false) ; il contrôlait aussi la position non arrondie alors que le monde publie la position arrondie. Le placement s'appuie désormais sur cells[].walkable, l'indicateur que WorldDescription expose lui-même, ce qui traite obstacles et mer par la même règle que celle publiée et supprime la divergence entre le test de placement (isPositionBlocked, point contre cercle) et la grille (isCellBlocked, centre de case). Sur 300 graines × 5 densités × 2 configurations d'obstacles (324 600 positions), 3 452 agents sur case non franchissable avant, 0 après ; la génération reste déterministe pour une graine donnée. Test de non-régression ajouté sur 5 graines.

  • docs/CI_CD.md §2.1 : documente les rulesets mainRules et developRules désormais actifs sur main et develop (PR obligatoire, squash seul, CI verte et branche à jour, pas de force-push, pas de suppression), les deux checks exigés et la raison de ce choix, ainsi que le retrait de required_signatures.

  • syne-mock : le déplacement ignorait la carte. Quatre défauts distincts, tous couverts par test/movement.test.js (+10 tests, 12 → 22) :

    • MovementService.move appelait isPositionBlocked sans cellSize : la branche shape comparait des coordonnées NaN et laissait traverser les sept formes d'obstacle, alors que la version circulaire bloquait ;
    • canEnter ne consultait que les obstacles et ignorait cells[].walkable : les agents traversaient la mer, et pouvaient y être placés ;
    • le coût de terrain était ignoré : une rivière (movementCost: 4) se traversait à la vitesse de la plaine. Le pas est désormais borné par le coût le plus élevé entre case de départ et case visée — entrer dans une rivière ralentit, en sortir aussi — mesuré à 3,83× plus lent qu'en plaine sur 4 318 déplacements en rivière contre 19 974 en plaine ;
    • un déplacement refusé était facturé : agent.energy baissait de moveEnergyCost même quand l'agent n'avait pas bougé, vidant les agents immobiles tout en déclarant une intention qu'ils ne suivaient pas. move() retourne {moved, blocked, destination} ; seul un mouvement effectif est facturé, et un blocage est reporté via outcome: 'blocked'.
    • cell-grid.js centralise l'indexation de cells[] (y * cellCountX + x) partagée par le placement et le déplacement. L'inversion x * cellCountX + y, auto-cohérente sur une carte carrée, faisait chevaucher des clés sur une carte rectangulaire (20×30 : (0,21) et (1,1) donnent la même clé 21) ;
    • l'arrondi est appliqué avant le test : valider une position non arrondie puis publier sa version arrondie pouvait placer un agent dans une case que le test disait libre.
    • Génération du monde inchangée : empreintes SHA-256 identiques avant/après sur 5 graines × 4 configurations, dont une carte rectangulaire 300×180 (cellCountX 38, cellCountY 23). npm test : 22/22.
  • syne-mock : trois défauts de cycle de vie, tous couverts par test/lifecycle.test.js (+8 tests, 22 → 30) :

    • start() laissait un run actif après un échec : loadReplay lève sur un fichier absent ou une ligne JSON malformée, et comme il était appelé après state = 'running' mais avant startTimer(), le résultat était state='running' avec timer=null — rien ne tickait, et tout appel ultérieur à start() était rejeté au motif que le run était déjà actif. Le chargement du replay précède désormais l'entrée en running. Après un échec, le monde reste préparé et acquitté, et un nouvel appel se comporte exactement comme le premier (vérifié sur le fichier absent et sur une ligne malformée) ;
    • this.events n'était pas borné : deux événements par agent et par tick étaient conservés pendant toute la durée du run. Sur 2 000 ticks × 50 agents, 202 200 événements restaient en mémoire (heap 66 Mo, RSS 168 Mo, processus isolé). diagnostics.maxEvents (défaut 2 000) borne désormais le tampon, qui ne retient que les derniers événements — ticks 1981 à 2000 pour le même run (2 000 événements, heap 18 Mo, RSS 84 Mo). La troncature vide jusqu'à mi-capacité plutôt que d'un seul élément, pour ne pas décaler tout le tampon à chaque émission. Le flux diffusé (WebSocket) reste la source faisant autorité, ce tampon est un diagnostic ;
    • goals[].age valait le tick absolu : la liste étant reconstruite à chaque tick, age était toujours égal au tick courant et ne disait rien de la durée d'un but. intentionSince suit le dernier changement d'intention et age devient le nombre de ticks écoulés depuis (15 à 24 au tick 2 000 d'un run de référence, au lieu de 2 000). Le marqueur reste interne : le snapshot publie une liste de champs explicite, donc intentionSince n'est pas exposé.
  • syne-mock/docs/CONTRACT_REFERENCE.md : deux affirmations du contrat étaient fausses ou incomplètes, et un consommateur pouvait s'y fier.

    • Le document annonçait « SYNE n'a pas de configuration de type de terrain : les cellules utilisent terrainType: "plains" ». C'est faux pour le mock, qui publie trois profils — plains (coût 1), river (coût 4) et sea (walkable: false, coût infini) — et dont le pas est borné par le coût de terrain. Le tableau des profils est ajouté, avec la règle d'entrée et de sortie de rivière et la frontière mer, en distinguant ce qui appartient au mock de ce qui appartient à SYNE.
    • La liste des actions à mapper en UENUM n'en citait que 5 sur 9 : SeekFood, SeekWater, Flee et Socialize manquaient. Les 9 sont listées, avec pour chacune sa condition de candidature et son premier tick de production, mesuré (graine 42, 50 agents) et non déduit. Deux impossibilités sont désormais explicites : Flee est structurellement inatteignable, car safetyDriftRate est positif alors que safety démarre à sa borne haute et ne peut donc jamais passer sous 0.5 ; Socialize n'est produite qu'au tick 943, donc hors du maxTicks: 400 par défaut.
    • Nouvelle section « Périmètre réel et simplifications assumées » : la mort, la perception, le contenu des communications, la décision de groupe, les livres, les territoires, les ressources locales, l'échelle temporelle et le pathfinding sont des éléments décoratifs, et simulation.events un tampon de diagnostic borné. Ces éléments sont publiés dans le contrat comme s'ils portaient du sens ; la section dit désormais ce qu'un client ne doit pas encore interpréter.
    • Le comportement d'un start refusé est documenté dans la section HTTP : le monde reste ready et réutilisable, donc un client peut réessayer sans appeler stop.
  • syne-mock : les agents passaient 146 ticks à ne rien faire au démarrage. Les besoins de départ étaient codés en dur dans createInitialAgents et curiosity partait de 0 : il fallait que curiosityDriftRate (0.002) amène la valeur au seuil d'exploration de 0.3, soit 150 ticks, ou 15 s à ticksPerSecond: 10. Couvert par 4 tests dans test/server.test.js (30 → 34) :

    • agentSimulation.needs.initial rend les six besoins de départ configurables, avec les domaines qu'ils bornent déjà : hunger/thirst/fatigue en 0..100, safety/social/curiosity en 0..1. Une valeur absente retombe sur l'état historique du mock, une valeur hors domaine est refusée à la génération du monde avec agents.needs.initial.<besoin> must be a finite number between 0 and <max>, comme les autres clés de configuration. Le placement ne lit que la graine et la carte : les besoins de départ décalent la trajectoire d'un agent, pas l'endroit où il apparaît ;
    • curiosity: 0.3 par défaut, exactement le seuil d'exploration, contre 0 auparavant : les 50 agents de la population de référence se mettent en mouvement au tick 1 et plus aucun Idle n'est produit. Eat: 100, Drink: 72, Rest: 234 et Socialize: 943 sont inchangés ;
    • curiosityDriftRate passe de 0.002 à 0.001, ce qui est le coût de ce décalage : explorer dès le premier tick sature curiosity bien plus tôt, et un besoin saturé garde une utilité supérieure à celle de Rest, qui devient alors plus jamais produit — mesuré sur 2 000 ticks, 0.002 supprime Rest du flux. À 0.001 le répertoire complet d'actions est conservé. Le taux est documenté dans le contrat avec cette dépendance ;
    • génération du monde vérifiée byte-identique avant/après sur 5 graines × 2 tailles (dont 300×180) ; run déterministe à l'identique sur 2 processus distincts (200 ticks, 50 agents).
  • Documentation technique V0.1 complète du monorepo (phases 0 à 5 du Plan documentation) :

    • générique racine : VISION, ARCHITECTURE, COMMUNICATION, GLOSSARY, ROADMAP, FAQ, README ;
    • gouvernance : GITFLOW, CI_CD, VERSIONING, LICENSE, CONTRIBUTING, CODE_OF_CONDUCT, SECURITY ;
    • docs/governance/* (ISSUES, PULL_REQUESTS, KANBAN) et templates .github/ (issues, PR, CI ci.yml, release release.yml) ;
    • SYNE : 16 docs + ADR-001/002/005–011 ;
    • ECHOS : 14 docs + ADR-001/002 (stack FastAPI + électron, calcul causal) ;
    • PRISM : 12 docs + ADR-001 (choix Godot) ;
    • ADR transverses : ADR-003 (API HTTP REST 5181), ADR-004 (WebSocket 5180) + 0000-template.
    • docs/presentation/ETHICS_AND_SCOPE.md.
  • Jalon U0 — Socle & gouvernance :

    • syne/ : solution .NET (Simulation.Core, Simulation.Console, Simulation.Core.Tests), global.json (SDK 10.0.400), modèle de configuration Annexe H + validation, flags CLI (--seed, --max-ticks, --world-size, --config, --headless) ;
    • echos/ : monorepo echos/ (API FastAPI :5000, package analyse, clients d'ingestion, echos-ui React/TS, tests pytest) ;
    • .gitignore, .editorconfig, branches Git Flow (develop), milestones dédupliqués, labels normalisés, CI remaniée (jobs filtrés syne/**, echos/**) ;
    • ECHOS-1 : structure code ECHOS livrée — paquet echos (API FastAPI create_app(), registre des 7 moteurs de métriques, placeholder ingestion), echos-ui (Vite + React + TS : lint, build, tests vitest/jsdom), 13 tests pytest couverture 100 %, jobs CI echos-python + echos-ui actifs ;
    • ECHOS-2 : contrats d'ingestion SYNE livrés — modèles pydantic WorldSnapshot/ExternalEvent (JSON camelCase), clients WsClient :5180 + ControlClient :5181 testés de façon déterministe sur golden files versionnés (41 tests pytest, couverture 97 %).
  • Divergence ECHOS annoncée et documentée (prototype C#/.NET + Django → FastAPI + web local React/Vite servie par FastAPI pour V0.1, SQLite/Parquet) ; le shell Electron est conservé (implémentation différée à un horizon ultérieur, correction 23/09/2026). [surclassé par ADR-007 (05/10/2026) : interface et shell retirés — voir Removed]

  • Correction Electron (23/09/2026) : reformulation « PAS de shell Electron » → « shell Electron conservé, implémentation différée post-V0.1 » dans ADR-001 ECHOS, docs/ARCHITECTURE.md (ECHOS + racine), FRONTEND_VISION.md (retiré du dépôt), README.md, docs/governance/ROADMAP.md, ISSUES.md, CHANGELOG.md (ECHOS), TRANSPORT_API.md (PRISM). Monographie non modifiée (snapshot figé). [surclassé par ADR-007 (05/10/2026)]

  • README et INSTALLATION reflètent l'état du socle U0 ; conteneurisation Docker d'abord reportée, puis retirée définitivement le 08/10/2026 (arbitrage A1, voir Removed).

Removed

  • Jalon T6 (docker compose up < 30 s) et conteneurisation retirés (08/10/2026, arbitrage A1) : plus de Dockerfile, de compose.yml, d'image GHCR ni de TODO de publication d'images — l'installation et l'orchestration sont portées par le Launcher (artefacts natifs linux-x64/win-x64, --check). Concerne docs/governance/ROADMAP.md (T6), docs/CI_CD.md §2/§4/§5, docs/ARCHITECTURE.md, docs/syne/ARCHITECTURE.md et docs/launcher/OBSERVABILITY.md ; les entrées « conteneurisation reportée » des changelogs antérieures restent historiques.
  • ECHOS — interface web et shell de bureau retirés (ADR-007, 05/10/2026) : suppression de echos-ui/ (React/Vite, 6 écrans), de echos-desktop/ (shell Electron, backend PyInstaller, packaging .deb/NSIS) et du workflow echos-desktop.yml. ECHOS devient une API headless (/ingest/run, /analysis/run|experiment|report, schéma SQLite v6) et la présentation revient aux fenêtres natives du Launcher (docs/launcher/adr/ADR-007-consoles-et-fenetre-analyse-natives.md). Les entrées « Electron conservé, différé post-V0.1 » (23/09/2026) sont surclassées par cette décision.
  • Site de documentation DocFX et publication GitHub Pages : docs/site/ (source DocFX), docs/landing/ (landing SaaS), docs/pages-dist/ (artefact d'assemblage commité alors que .gitignore l'ignorait, origine des fichiers styles/*.js perpétuellement modifiés) et le workflow .github/workflows/docs-pages.yml. La branche gh-pages est réinitialisée sur une page unique. Le nettoyage supprime 51 fichiers suivis ; l'entrée docs/pages-dist/ devenue inutile est retirée de .gitignore. Les documents markdown écrits à la main (docs/syne, docs/echos, docs/prism, docs/adr/, docs/governance/) sont conservés : ce sont les sources, pas des artefacts.

Deprecated

  • (aucun)