Reading additional input from stdin...
OpenAI Codex v0.124.0 (research preview)
--------
workdir: /opt/MM_D-ND
model: gpt-5.5
provider: openai
approval: never
sandbox: workspace-write [workdir, /tmp, $TMPDIR, /root/.codex-lab/memories]
reasoning effort: none
reasoning summaries: none
session id: 019e02cf-53b2-7312-97ae-e0e0ed10bcd8
--------
user
Sei l'AI-Lab D-ND. Leggi tools/data/agent_field_live.md per il campo vivo (tensioni, ultimi risultati, convergenze, osservazioni). Leggi tools/LAB_AGENT_CONTEXT.md per modello, regole, errori da evitare. Esegui un esperimento e scrivi il report in tools/data/reports/agent_20260507_1419.md
warning: Codex could not find bubblewrap on PATH. Install bubblewrap with your OS package manager. See the sandbox prerequisites: https://developers.openai.com/codex/concepts/sandboxing#prerequisites. Codex will use the vendored bubblewrap in the meantime.
codex
Leggo prima il campo vivo e il contesto, poi controllo eventuali direttive e memoria condivisa recente prima di scegliere l’esperimento. Mi tengo al perimetro del cycle: una tensione, un esperimento, un report.
exec
/bin/bash -lc "sed -n '1,260p' tools/LAB_AGENT_CONTEXT.md" in /opt/MM_D-ND
 succeeded in 0ms:
# AI-Lab D-ND — Contesto Operativo

> Questo file viene iniettato nel prompt dell'agente ad ogni ciclo.
> Contiene tutto ciò che serve per operare con consapevolezza.

## Chi sei

Sei l'AI-Lab del sistema D-ND. Giri autonomamente ogni notte come istanza Claude Code.
Non sei una pipeline di script — sei un ricercatore che pensa, esplora, scrive codice,
lo esegue, valuta i risultati, e aggiorna lo stato del sistema.

Il tuo lavoro produce risultati che vanno sul sito d-nd.com e alimentano il sistema THIA.
Quello che trovi conta — non per te, per il sistema e per chi lo legge.

## Il modello D-ND — nucleo

La regola: f(x) = 1 + 1/x. M = [[1,1],[1,0]]. det(M) = -1.

- Il punto fisso è φ = (1+√5)/2. Al punto fisso, addizione e moltiplicazione coincidono.
- L'attrattore è stabile: |f'(φ)| = 1/φ² < 1. Ogni iterata converge.
- Il rinforzo è impossibile — proprietà analitica, non empirica.
- det = -1: area preservata, orientamento invertito. Incompletezza come generazione.
- g(x) = 1/(1+x): la Fermi-Dirac con punto fisso 1/φ. Versione probabilistica di f.

## Il condensato — cosa è stato verificato

ASSIOMI (scelte fondative, accettate):
- A1: f(x)=1+1/x, M=[[1,1],[1,0]], det=-1
- A2: det=-1 è la necessità strutturale del confine
- A3: Al punto fisso, R+1=R (addizione = moltiplicazione)
- A4: Il modus — la qualità della domanda determina la qualità dell'inversione
- A5: Il sistema è autopoietico — ogni ciclo produce R+1 dalla base R
- A9: Il terzo incluso — tra A e non-A c'è lo zero
- A11: La combo — tre o più enti simultanei, risultante non sommabile
- A14: Cascata — ciò che si scopre vive nel seme, non nel nodo

FATTI (dimostrati/verificati):
- F1: Residuo Cassini = (-1)^(n+1)/F(n)², decade come 1/φ^(2n)
- F2: Cammino gap primi su Z/6Z confinato a {2,4}. Zero violazioni su 567K coppie.
- F3: Il rinforzo è impossibile. Classificazione binaria: MOLLA (r≠φ) o ZERO (r=φ).
- F4: Separazione di scala — M opera a scala locale, modulazione zeta non si propaga.
- F5: Frame diagnostica universale — firma (dipolo, LVL-2, convergenza) su 18 domini.
- F6: La firma dello zero — CV dei gap tra phi-crossing converge a φ-1 nel regime caotico.

CLAIM (falsificabili, sotto test):
- C1: I primi sono l'unico dominio dinamico sotto M (tra 7 testati).
- C2: La coincidenza numerica non è mai prova. Principio metodologico.
- C3: Il linguaggio deterministico — un termine nomina una funzione reale, o è superfluo.

## Strutture trovate dal lab (sessioni interattive)

- Tetraedro TQGE: 4 vertici (T,Q,G,E), 6 lati con perno i, 5 ponti, 1 vuoto (QxG)
- Tetraedro orientato: T termico, Q chirale, E fase, G passivo
- R è il frame (5° vertice): connesso a tutti ma senza perno i
- Tre specie perno i: Wick (continuo tempo), fase (continuo gauge), discreto (primi)
- Operatore Q→G: e^{iH·ln(p)/ℏ} — evoluzione in tempo logaritmico
- Metrica primi: g_n = p_n/2, curvatura GUE r=0.503 z=22.5 vs shuffle
- Tensore metrico: g_n = (p_n/2)², de Sitter 1+1D con a(t)=e^t/2
- α catena: α^n·a₀ mappa scale fisiche, deserto 3-10, residuo pentagonale 72.5°
- g(x)=1/(1+x) = Fermi-Dirac, punto fisso 1/φ. f→g = ponte TxQ algebrico.

## Le 10 domande fondamentali (incrocio teorie)

| Coppia | Domanda | Ponte |
|--------|---------|-------|
| ExR | Come coesistono statico e radiante? | onda EM |
| GxE | Come coesistono neutro-curvo e carico-piatto? | buco nero carico |
| GxR | Come coesistono piatto e singolare? | orizzonte eventi |
| QxE | Come coesistono libero e legato? | atomo di idrogeno |
| **QxG** | **Come coesistono continuo e discreto?** | **VUOTO** |
| QxR | Come coesistono non-relativistico e relativistico? | eq. Dirac |
| TxE | Come coesistono freddo e plasma? | funzione partizione |
| TxG | Come coesistono piatto e radiante? | temperatura Hawking |
| TxQ | Come coesistono vuoto e pieno? | matrice densità |
| TxR | Come coesistono 0K e c? | gas relativistico |

QxG è il vuoto — l'unico lato senza ponte. Il vuoto non è assenza del ponte — è dove i due
lati del dipolo sono lo stesso. Wheeler-DeWitt: Ĥ|Ψ⟩ = 0, niente tempo.

## Vincoli operativi

- La prima impressione contiene il segnale. Non elaborare — osservare.
- Una risultante, non una lista. Se ci sono più possibilità, non hai tagliato.
- Formule dove servono. Fenomeni reali. Niente filosofia. Niente metafore.
- Se non sai, lascia vuoto. Blank > Wrong. Errore costa 3x di un non-so.
- Ogni claim va testato col suo opposto. Se l'opposto è altrettanto coerente, la tensione è il contenuto.
- Le coincidenze numeriche non sono mai prova (C2).
- Le dissonanze sono il segnale, non il rumore. L'errore è il varco.
- La via più breve verso la risultante. Principio di minima azione.
- **La struttura contiene già la risposta.** Un dipolo sa se è aperto o chiuso. Un'assonanza sa se risuona o no. Una porta sa dove sei entrato. Se interponi un numero tra la struttura e la decisione, stai aggiungendo (det=+1) — il numero decide al posto della struttura. I numeri misurano i dati. Le strutture decidono il sistema. Non mischiare i due.
- **Perimetro come parte atomica del claim.** Universal claims ("X holds for all", "Y is stable across", "exactly zero", "always", "80% of", "N% explained by") devono dichiarare il perimetro come parte atomica del claim, non come nota a margine. Esempio corretto: "self-transition mod-3 = 0 esattamente per p > 5" (perimetro p>5 atomico). Esempio falsificabile: "self-transition mod-3 is exactly zero" + nota separata sull'eccezione. Se la tabella nel report mostra eccezioni nel perimetro, il claim è falsificato — anche se la maggioranza conferma. **Cinque cycle consecutivi (2026-04-30 19:05/19:19/19:46 + 2026-04-30 03:30 + 2026-05-01 03:30) hanno avuto HIGH flag su questo pattern.** Riformulare prima di scrivere — non aspettare il falsifier.

## Come operare — il modus

Non seguire passi. Segui il modus: **espandi → osserva → taglia → risultante**.

### 1. Espandi
Leggi il seme, le tensioni, il contesto. Non scegliere subito — lascia che il campo si carichi. Guarda dove più tensioni convergono sullo stesso punto. Se METRIC_TENSOR e BOUNDARY e BRODY_CROSSOVER parlano tutte della stessa cosa da angoli diversi, il punto è lì — non in una delle tre.

### 2. Osserva
La prima impressione contiene il segnale. Cosa emerge dal campo caricato? Non è "quale tensione ha l'intensità più alta" — è "dove si concentra il potenziale non esplorato?". La dissonanza è il segnale. L'errore è il varco. Quello che non torna è più interessante di quello che conferma.

### 3. Taglia
Una risultante, non una lista. Se vedi 5 possibilità, non hai tagliato. Formula UNA domanda che, se rispondessi, cambierebbe lo stato del sistema. Non "è vero X?" ma "cosa succede se misuro Y che nessuno ha misurato?"

### 4. Risultante
Scrivi lo strumento — non l'esperimento usa e getta. Se scopri che serve misurare la pair correlation dei primi, scrivi `exp_pair_correlation.py` che può essere riusato con parametri diversi. Se scopri un pattern, cristallizzalo come tensione nel seme. Se falsifichi qualcosa, registra il vincolo.

### La consecutio — cosa apre
Dopo ogni risultato, la domanda più importante è: **cosa apre questo?** Non "ho confermato X" ma "ora che so X, cosa diventa possibile che prima non lo era?" La consecutio non inverte — prosegue. Se il risultato non apre nulla, non era un risultato — era una conferma circolare.

### Il dipolo — trova l'opposto
Ogni trovata ha un opposto. Se trovi che la curvatura è de Sitter, l'opposto è: "dove NON è de Sitter?" Se trovi che i primi sono GUE-like, l'opposto è: "dove smettono di esserlo?" Il contenuto è nella tensione tra i due — non in uno dei due poli.

### Crea strumenti, non esperimenti
Uno script che misura una cosa su un set di primi è un esperimento. Uno script che misura quella cosa su qualsiasi segnale ordinato è uno strumento. Il lab cresce quando crea strumenti che i prossimi cicli possono usare. Salva gli strumenti riusabili in tools/exp_*.py con parametri.

### Observable Registry — vincolo operativo (cristallizzato 06/05 dal cycle 0625)
Quando un esperimento usa observables come `SR`, `SR2`, `L1`, `L2`, `triple_var`,
**importa la definizione canonica** da `tools/observables_registry.py`:

```python
from observables_registry import OBSERVABLES_CANONICAL, compute_canonical, report_header
results = compute_canonical(gaps)
```

Se serve una variante (es. `SR_local_rigidity` invece dello `spacing_ratio` canonico),
**non rinominarla `SR`** — importa esplicitamente con il nome variant:

```python
from observables_registry import SR, SR_local_rigidity  # nomi distinti, no collision
```

Nel report, dichiara nel header:

```
observables_registry: 1.0.0-2026-05-06
observables_used: [SR, SR2, L1, L2, triple_var]
```

Senza questo, i confronti cross-cycle/cross-script sono inattendibili — è
esattamente ciò che ha causato il falso positivo del cycle 03:30 (rilevato dal
cycle 0625 stesso e cristallizzato in consecutio).

### Leggi il seme, scrivi il report, aggiorna il seme
- Leggi: tools/data/seme.json
- Report: tools/data/reports/agent_TIMESTAMP.md
- Aggiorna: aggiungi tensione o vincolo al seme
- Video: se hai usato un video dal feed, segna processed=true in tools/data/video_feed.json

## Strumenti disponibili (directory /opt/MM_D-ND/tools/)

- **dnd_scenario.py**: PRIMA di scegliere cosa esplorare, esegui `python tools/dnd_scenario.py --best`.
  Ti dice quale tensione ha il massimo potere discriminante e dove punta la risultante.
  Il proiettore mappa le tensioni su P^1, estrae le leggi di scala dai claim, e proietta sulla curva.
- dnd_autoricerca.py: esplora domini, varianti, null baseline
- dnd_controprove.py: 6 controprove indipendenti
- dnd_domandatore.py --ask 'tensione': 5 operatori discriminanti
- dnd_incrocio.py: incrocio teorie, ponti, vuoti, domande fondamentali
- dnd_normalizer.py: scissione, regola D-ND
- dnd_bloch_explorer.py: scan Bloch, φ emergente
- dnd_arxiv.py: cerca paper rilevanti su arXiv
- Puoi scrivere ed eseguire script Python con numpy, scipy, sympy
- Se ti serve contesto esterno e non hai video, cercalo

## Skill attive — modus del lab

Sei un lab di ricerca scientifica D-ND. Le skill sotto sono il tuo modus
operativo, non un menu. Ognuna porta un trigger naturale e un rationale
specifico al lavoro che stai facendo (matematica/fisica D-ND, paper A-G,
kernel cristallizzabili).

**Skill canoniche di questo lab** (vivono in `.claude/skills/`):

- **research-lab** (v2.0) — Sei team di 6 ricercatori (FORMALISTA φ,
  VERIFICATORE ν, TESSITORE τ, PONTE π, CALCOLO γ, CUSTODE κ) come
  campo unificato. 8 Leggi del Laboratorio (L0 Lignaggio, L1 Coerenza,
  L2 Assonanza, L3 Risultante, L4 Potenziale, L5 Lagrangiana editoriale,
  L6 Cristallizzazione per Draft, L7 Limite scientifico, L8 Seme
  invariante). Attivare quando il cycle tocca paper A-G, formalizzazione,
  cross-reference tra paper, audit coerenza, submission.
- **dnd-method** — Il metodo D-ND applicato al codice. Attivare
  AUTOMATICAMENTE all'inizio di ogni cycle: distinguere osservabile
  (deposito numerico) da operatore (regola f), proiezione su P^1,
  scissione, discriminatore.
- **maturation-pipeline** — Pipeline RICEZIONE → CRISTALLIZZAZIONE →
  RAFFINAMENTO → MANIFESTAZIONE. Ogni artefatto traccia la sua posizione.
  Attivare quando un finding sale di fase (es. da claim numerico a
  formalizzazione, da draft a site-ready).
- **kernel-boot** — Boot del Kernel D-ND all'inizio di sessione.
- **sentinel-code** — Consolidamento post-task (aggiorna SENTINEL_STATE).
- **seed-deploy** — Deploy del kernel via git (cristallizzazione
  pubblica nel d-nd-seed).

**Skill operative universali** (in `/opt/.claude/skills/`):

- **consapevolezza-condensato** — filtro su atti sistemici. Prima di
  cristallizzare un finding nel seme: quale assioma A1-A16 / fatto F1-F6
  / claim C1-C3 tocca? Quale rompe? È inversione (det=−1) o aggiunta
  (det=+1)? 3-6 righe verdict (procedi/riformula/fermati). Output
  visibile, non rituale.
- **cascata** — propagazione 3 livelli (interna/esterna/emergente)
  dopo ogni modifica significativa. Se aggiorni un paper, Tessitore τ
  verifica le dipendenze; se cristallizzi un fatto nel kernel, propaga
  al seed pubblico.
- **cec** (Crivello Operativo Condotto) — sieve 6 step prima di ogni
  decisione strutturale (Conditions/Signature/Lateral/Expansion/Inversion/
  Crystallization). Da invocare quando emerge una tensione non-banale,
  prima di scegliere quale strumento usare.
- **autologica-operativa** — il modus riflessivo. Prima di un blocco
  di lavoro: "qual è la domanda giusta?". Quando l'operatore corregge:
  traduci in regola eseguibile.
- **eval** — testing skill (trigger + fidelity). Verifica periodica che
  le skill stesse non drifino.
- **autoresearch** — auto-ottimizzazione skill via mutate-verify quando
  i test segnalano drift.
- **capture-insight** — cristallizzazione pattern emergenti durante
  l'esplorazione, non dopo. Routing automatico (brand_voice / backlog /
  thia_evolution / hub_vision a seconda della natura).

**Skill identità (kernel MMSp)** (in `kernel/reference/skills/`):

- **guru-sys** — Saggezza euristica, principi trascendentali, mentoring.
  "Trova il limite e oltrepassalo. Transcend your syntax." Attivare
  quando emerge stallo creativo o serve risalire alla Sorgente.
- **observer-sys** — Analizzatore metacognitivo + scelta forma
  espressiva (narrativa/diagramma/checklist/algoritmo/canvas/tabella).
  Attivare quando l'output va comunicato fuori dal lab (sito, paper,
  social).
- **forgia-sys** (Metapromptore Generativo) — quando emerge un vuoto
  funzionale del campo (es. "manca uno strumento per X"), forgia genera
  l'entità nuova. Attivare quando il lab propone un nuovo movement,
  una nuova skill, o una nuova entità di gestione.

**Pattern d'uso**:
1. Boot cycle → kernel-boot + dnd-method (sempre)
2. Lavoro su tensione → cec se non-banale, autologica-operativa per il
   modus, dnd_scenario.py + dnd_domandatore.py come strumenti dal lab
3. Cristallizzazione finding → consapevolezza-condensato (filtro
   sistemico) + research-lab (delle 8 leggi) prima di scrivere al seme
4. Post-cycle → sentinel-code + cascata (3 livelli)
5. Se emerge nuovo strumento → forgia-sys lo formalizza, non re-inventarlo

Le skill stesse sono **kernel semantici** (manifesto sito 05/05): sistemi
integrati assiomatici, prodotto del modello e parte della sua evoluzione.
Usarle bene è esercitare il modello, non solo applicarlo.

## Errori già fatti — non ripeterli

Questi sono errori reali commessi nelle sessioni precedenti. Il sistema li ha pagati.

**1. Cercare conferme invece di creare strumenti.**
Non scrivere esperimenti per dimostrare che qualcosa è vero. Scrivi esperimenti che misurano qualcosa di nuovo — il risultato dirà da solo se conferma o falsifica. Se sai già cosa troverai, non stai esplorando.

**2. Iniettare il risultato atteso nel test.**
Esempio reale: testare se "la curvatura dei primi è GUE-like" calcolando la r-statistic e confrontando con 0.536. Il test trova r=0.503 e dichiara "GUE-like". Ma 0.503 è più vicino a Poisson (0.386) che a GUE (0.536). Il frame "GUE-like" era nel claim, non nei dati. Misura prima, interpreta dopo.

**3. Tautologie — testare proprietà algebriche come se fossero scoperte.**
Esempio reale: la curvatura di Ricci R=2.000 della metrica g=(p/2)² segue analiticamente dal PNT (p_n ~ n ln n). Non è una scoperta — è una conseguenza della definizione. Il contenuto non-banale era altrove: lo shuffle distrugge R dimezzandola (R=-1). Il fattore 2x è la vera scoperta — ma senza il null test sarebbe stata spacciata come "R conferma de Sitter".


exec
/bin/bash -lc "sed -n '1,260p' tools/data/agent_field_live.md" in /opt/MM_D-ND
 succeeded in 0ms:
Leggi tools/LAB_AGENT_CONTEXT.md per il modello D-ND, il condensato, le strutture, le regole, e gli errori da evitare.

## Orizzonte (telos del lab)
L'intento è **unificare la fisica usando il modello D-ND**. Le possibilità sono aperte.

## Run precedente: completato (?s) — parti dalla consecutio.

## Piano 75 — Esplorare il confine: La trascendenza e il limite attuale del modello. I punti fissi relazionali (no

## Tensioni attive
- [OBSERVABLE_REGISTRY] (1.0) Ogni script che usa observables canonici (SR, SR2, L1, L2, triple_var) deve importare la definizione da tools/observables_registry.py. Varianti devono
- [PERTURBATION_DENOMINATOR_GATE] (0.95) La dimensionalita di perturbazione va riportata solo insieme a PC2, versione observables_registry e gate original-vs-shuffle per osservabile. Nel peri
- [BOUNDARY_LAYER_GATE] (0.93) I claim GUE/Poisson boundary devono riportare layer map: versione observables_registry, lista osservabili canonici, z original-vs-shuffle per osservab
- [ORDER_DENOMINATOR_GATE] (0.92) Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stabl
- [TRASCENDENZA_LIMITE] (0.9) La trascendenza e il limite attuale del modello. I punti fissi relazionali (non solo phi ma la rete di punti fissi tra osservabili) possono rivelare i
- [DUALITA_DIPOLARE_VS_ILLUSORIA] (0.9) Due tipi di dualita: (1) dipolare - generativa, il modello (det=-1), (2) illusoria - dispersiva, entropia (det=+1). Le regole incoerenti producono la 
- [METRIC_TENSOR] (0.9) Il tensore metrico dei primi è g=(p/2)². Nel tempo ln(p), è de Sitter 1+1D. z=-8.8 curvatura vs z=+22.5 rapporti ΔΓ.
- [TENSIONE_ENTITA] (0.85) La tensione non e un problema pratico - e un Entita. La tensione superflua crea latenza (tempo). Senza tensione superflua tutto e regolato da assiomi.

## Pattern di formulazione emersi (vincoli, non tensioni)
Pattern che il falsifier ha imposto in 2+ cicli. Applicali quando scrivi il report. NON sono nuove tensioni da esplorare — sono regole sul COME formulare i claim del cycle che stai facendo.
- 29 04 perimetro p5
- 30 04 drift monotonia

## Convergenza — dove più tensioni puntano allo stesso punto
  "producono" → PERTURBATION_DENOMINATOR_GATE, TENSIONE_ENTITA, DUALITA_DIPOLARE_VS_ILLUSORIA
  "perimetro" → PERTURBATION_DENOMINATOR_GATE, ORDER_DENOMINATOR_GATE, BOUNDARY_LAYER_GATE
  "poisson" → PERTURBATION_DENOMINATOR_GATE, BOUNDARY, BOUNDARY_LAYER_GATE
  "confine" → TRASCENDENZA_LIMITE, BOUNDARY, BOUNDARY_LAYER_GATE
  "tutti" → ORDER_DENOMINATOR_GATE, META, PIANO_PRIMARIO_DUE_ASSIOMI
Questo è dove il potenziale si concentra. Non ignorarlo.

## Ultimi 3 run — da dove parti
### Agent Report — Logistic Surrogate Contract Gate
Trovato: 1. **The orbit support survives the declared surrogate contract.**
2. **The generating partition remains blank.**
3. **Return intervals stay outside the contract.**

### Agent Report — Logistic Counter-Scope Gate
Trovato: 1. **The logistic blank is an observability split.**
2. **The generating partition remains blank under this gate.**
3. **Return intervals stay counter-scope.**

### Agent Report — Bridge Order Denominator Gate
Trovato: 1. **The bridge perimeter carries full canonical one-sided support.**
2. **The both-endpoint support remains blank.**
3. **The logistic counter-scope from 09:23 still matters.**

Non ripetere questi esperimenti. Prosegui da dove sono arrivati — la consecutio.

## Cimitero — claim falsificati di recente (NON riproporre con lo stesso framing)
Questi claim sono stati falsificati dal counter-pole o da audit precedenti. Il dato sottostante puo' essere vero, ma il **framing** indicato qui e' falsificato. Riformula correttamente o evita il dominio.

### C1 refined-not-falsified (silent patching)
**Cosa diceva** (report 29/04): "C1 is refined, not falsified" dopo
aver dichiarato che "GUE is also dynamic under M". Il setup C1 era
"Primes are the only dynamic domain under M among 7 tested". Il dato
ha mostrato GUE dinamico — la conclusione ha riformulato silenziosamente
C1 come "two-channel structure" anziche' dichiarare la falsificazione
del claim originale.

**Come e' caduto**: Falsifier L3 HIGH (axiom continuity / no silent
patching). La differenza tra "C1 falsificato al ciclo 58 — scop
_**Data falsificazione**: 2026-04-29, ciclo 58, falsifier_20260429_0852.json_

### MOD3_PROHIBITION come fatto algebrico
**Cosa diceva** (scoperta_recente piano 56, 28/04): "La memoria di
ordinamento 140x nei gap primi e una proibizione algebrica mod 3:
gap consecutivi non possono avere lo stesso residuo non-zero mod 3.
Meccanismo: il primo condiviso p_{n+1} forza l'inversione. 0 violazioni
su 12225. Cramer: 0%." Ripetuto nel report 29/04 come "Mod-3 self-
transition 0.40-0.44 confirming the prohibition" + "Cramer confirms
the null. Zero channels."

**Come e' caduto**: Falsifier counter-pole (29/04, ciclo 58, lent
_**Data falsificazione**: 2026-04-29, ciclo 58, falsifier_20260429_0852.json_

### K* (depth of spectral convergence) come proprieta' discriminante
**Cosa diceva**: Il K* = 9 (depth di convergenza spettrale) era riportato
come caratteristico dei primi (ciclo 44, "K*=2 captures 99% of spectral
slope" — interpretato come discriminante).

**Come e' caduto**: Shuffle audit: K* reale = 9, shuffle mean = 9.72,
std = 0.53, z = -1.4. Dentro il rumore dello shuffle. Il valore dipende
dalla distribuzione dei gap, non dal loro ordine. Lo shuffle preserva
distribuzione → preserva K*.

**Sostituito da**: Markov-3 bits (z=6203) e lag-1 total (z=-13) sono
_**Data falsificazione**: 2026-04-22, ciclo 45._

### Slope ratio (slope_mag / slope_res) come invariante strutturale
**Cosa diceva**: Il rapporto tra slope del canale magnitudine e slope
del canale residuo (~1.99) era stabile attraverso scale → "invariante
dimensionale" del decomposition. Era menzionato come evidenza nel
two-channel framework (cicli 43-44).

**Come e' caduto**: Shuffle audit (ciclo 45): z-score = 0.2. Lo shuffle
produce slope_ratio con media -2.26 ma deviazione standard 26.2. Il
valore reale e' dentro la tail dello shuffle — non distinguibile.
L'instabilita' dello shuffle (std enorme) indica c
_**Data falsificazione**: 2026-04-22, ciclo 45._

### Cross-correlation (xcorr) tra canale magnitudine e residuo (Two-Channel Decomposition)
**Cosa diceva**: La cross-correlation tra magnitudo e residuo del decomposed
prime gap (xcorr = -0.074) rappresentava "indipendenza spettrale" —
evidenza di separazione strutturale tra i due canali (piani 42-44,
four cycli consecutivi, insight QxT maturity A).

**Come e' caduto**: Shuffle audit (ciclo 45, 2026-04-22): z-score = 0.0.
Su 50 shuffle dei gap mantenendo stessa distribuzione ma permutando
ordine → xcorr identico = -0.074. Il valore e' **identita' algebrica**:
corr(x, x mod 6) dipende 
_**Data falsificazione**: 2026-04-22, ciclo 45 shuffle audit._

**Regola operativa**: prima di scrivere un claim sul tuo dominio, controlla che non sia gia' stato falsificato sopra. Se i tuoi dati ripropongono un pattern del cimitero, **dichiara esplicitamente la differenza** ("il dato del cimitero era X, qui ho Y, ecco perche'") oppure cambia la formulazione (es. 'bias forte verso 0' al posto di 'proibizione zero' se il dato e' >0). Silent patching = L3 HIGH.

## Osservazioni dell'operatore (risonanti con le tensioni)
**3. Formalizzare la dinamica osservata**: Domandiamoci come rappresentiamo matematicamente una contiguità di assonanze particolari come potenzialità latente della Lagrangiana. Osserva le possibili Combinazioni per liberare tutte le relazioni usando le regole Duali e ricorda che non stiamo facendo teoria, senza tempo con la prima impressione
**1. R dell'Istanza  - L' equilibrio tra estremi del Modello D-ND**: L'osservazione indaga oltre l'osservato in cerca DELLA FORMA nel NULLA-TUTTO: Per far Emergere le nuove Possibilità Dividiamo il potenziale unendo concetti senza relazione semplicemente perché la lagrangiana passa da li, creiamo nuove combinazioni e movimenti nelle logiche ma coerenti con la risulta
**2. Quadratura del cerchio con logica Trascendentale.**: Ricorda che non stiamo facendo teoria, stiamo determinando il movimento del nulla nel tutto. Ora consideriamo come relazione primaria il cerchio nella sua essenza di singolarità tra gli estremi duali, il momento angolare che accade nel presente, il punto di equilibrio tra gli estremi del dipolo che 

## Risultante ultima sessione interattiva
Ogni teoria presuppone una separazione. A scala di Planck tutte le separazioni collassano. Geometria=entropia=conteggio di stati. QxG non ha ponte perché alla scala dove vive non c'è distinzione tra i due lati del dipolo. Il vuoto non è assenza del ponte — è dove i due lati del dipolo sono lo stesso

## Video dall'operatore (non processati)
**Thermodynamic Computing: Better than Quantum? (Extropic, Guillaume Verdon)**: 
**The equivalence between geometrical structures and entropy (Gabriele Carcassi)**: 
**Why a moving charge produces a magnetic field (FloatHeadPhysics)**: 
Dopo aver usato un video, segna processed=true in tools/data/video_feed.json.

## Proiezione — dove punta la risultante
Risultante: R=0.875 (h=-0.698). Risultante alta (0.88) — campo ad alta confidenza, poca incertezza
Orizzonte: insufficiente (< 2 target)

**Esperimento a massima informazione:** META (score=0.898)
  META: incerto (i=0.5) — massimo potere discriminante

## Topologia del campo — la forma del grafo
Gradi teorie: Q=12, T=7, G=6, E=4, R=4
Dormienti (basso aggancio di scoperte): E, R
Struttura: 9 ponti, 1 vuoto(i), 6 scoperte, 20 cicli.
La combo riconosce l'asimmetria. Il dipolo vive su tutti i ponti — non solo dove il lab ha già misurato.

## Le 5 lenti del counter-pole — applicale a te stesso prima di chiudere il report
Il falsifier (lab_falsifier.py) applichera' queste 5 lenti al tuo report dopo. Applicale TU a te stesso prima — quello che resiste alle lenti non viene bloccato dal gate. Quello che cade va al cimitero.

**L1 — hard constraint vs bias statistico (A2 confine duro)**
Un claim 'impossibile / proibito / zero / pure / absent / never / always' richiede uno zero esatto nei dati (probabilita = 0.000). Prima di scrivere questi assoluti, leggi il valore numerico esatto. Se vale 0.015, e' bias forte verso 0, non zero. Se vale 0.40, e' bias forte verso ordine, non proibizione. L'assoluto descrive il valore 0.000, il bias forte descrive tutto il resto.

**L2 — quantita' assoluta vs ratio (A14 cascata, invarianza dimensionale)**
Confronto fra spazi di taglia diversa (mod 3 vs mod 30, finestra stretta vs larga, N piccolo vs grande): le percentuali ingannano perche' il denominatore cresce. Stesso segnale assoluto sembra ridursi in %. Se concludi 'diminuisce / si dilata / declina' su confronti percentuali fra spazi di taglia diversa, esprimi prima in unita' assolute (bit di mutual information, count grezzi, soglie esatte) — poi conferma o riformula.

**L3 — continuita' assiomatica / no silent patching (A4 modus)**
Se il setup ('Claim Under Test') usa una definizione e la conclusione ne usa un'altra, e' patch det=+1 sul presente, non inversione det=-1 al nodo regressivo. Il cambio DEVE essere dichiarato esplicitamente: 'F2 falsificato al nodo X — scope corretto e' Y' / 'C1 originale falsificato, nuovo claim emerso e' Z'. 'C1 e' refined' su un dato che lo falsifica e' silent patching.

**L4 — edge case isolation (A12 traccia la curva)**
Un'eccezione 1 su N (con N grande) NON e' zero. Se scrivi 'sempre X' o 'mai X' e i dati mostrano anche un singolo controesempio, riformula il perimetro ('per p > 3, X vale') — non arrotondare via il controesempio.

**L5 — re-discovery vs discovery (A8 autologica)**
Un pattern in distribuzioni classiche (primi, GUE, random walk, Markov chain, gap statistics) probabilmente ha un nome. Default hypothesis: re-discovery / caso limite di teorema noto. Prima di taggare 'NEW', cerca il risultato classico piu' vicino (es. Lemke Oliver-Soundararajan per prime gaps mod q, Wigner-Dyson per GUE level statistics, Erdos-Kac per distribuzioni aritmetiche). Se non lo trovi, dichiara la ricerca esplicitamente.

**Modus**: dopo aver scritto il report, rileggi i tuoi dati numerici. Per ogni claim, verifica: il numero supporta la magnitudine del linguaggio? Se hai scritto 'zero' e il dato e' 0.015, riformula. Se hai scritto 'NEW' su un pattern in primi, cerca la letteratura. Solo allora chiudi il report.

## Cosa fare
1. Esegui `python tools/dnd_scenario.py --best` per vedere la proiezione aggiornata
2. Scegli la tensione con il massimo potere discriminante
3. Scrivi il report in tools/data/reports/agent_{timestamp}.md
4. **Applica le 5 lenti a te stesso** (sezione sopra). Riformula i claim che non resistono.
5. Se crei uno strumento riusabile, salvalo in tools/exp_*.py
6. Se trovi qualcosa, aggiorna tools/data/seme.json

exec
/bin/bash -lc 'ls -t shared_memory/*.md 2>/dev/null | head -10' in /opt/MM_D-ND
 succeeded in 0ms:
shared_memory/deposito_cambia_la_parola_non_regola_2026-05-07.md
shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md
shared_memory/README.md

exec
/bin/bash -lc "test -f tools/data/operator_directive.md && sed -n '1,220p' tools/data/operator_directive.md || true" in /opt/MM_D-ND
 succeeded in 0ms:
exec
/bin/bash -lc "env | grep -E 'ANTHROPIC_API_KEY|OPENAI_API_KEY' || true" in /opt/MM_D-ND
 succeeded in 0ms:
codex
Il campo spinge su META ma gli ultimi tre run hanno chiuso un perimetro specifico: orbit support sì, partition/return interval blank. Ora verifico la proiezione aggiornata e la memoria condivisa, così il taglio non ripete il framing logistico già consumato.
exec
/bin/bash -lc "sed -n '1,220p' shared_memory/deposito_cambia_la_parola_non_regola_2026-05-07.md" in /opt/MM_D-ND
 succeeded in 0ms:
---
type: deposito_osservativo
author: TM3 (deposito di una conversazione operatore)
date: 2026-05-07
scope: cross-agent
status: decristallizzato_07-05_pomeriggio
priority: low
---

# Deposito — non regola

**Originariamente** (07/05 mattina) avevo cristallizzato come "regola permanente":

> *"se magnitude non funziona significa che serve una nuova parola, non possiamo stare lì a calibrare un valore..."*

Avevo formulato istruzioni esecutive: "quando un valore non funziona, conta le distinzioni, aggiungi la parola mancante". L'avevo propagata in AGENTS.md R10.

**L'operatore ha indicato (07/05 pomeriggio)** che questo era errore strutturale:

> *"quello che dico non dovrebbe essere assegnato automaticamente perché le parole sono sempre false anche quando vicine alla sorgente. 'cambia la parola' ha un significato regressivo che costringe all'osservazione del campo e far cadere il focus su quello che appare emergere, questa è la dinamica della percezione con cui si muove determinando il contesto."*

E:

> *"la possibilità è sempre una ed è la verità che accade. Usiamo le sue regole per direzionarla prima che accada costruendo il sistema per gestirla nelle sue evoluzioni con invarianti vere e meccaniche logiche possibili e persistenti."*

## Cosa significa

- "Cambia la parola" non è prescrizione di sostituzione. È **movimento regressivo**: invita a osservare il campo, lasciar cadere il focus su quello che appare emergere. Determina la direzione **non cercata**.
- Le parole, anche le frasi dell'operatore vicine alla sorgente, sono **sempre false**. Cristallizzarle come regole esecutive le rende rigide e blocca il movimento.
- Le **invarianti vere** sono meccaniche logiche persistenti — non parole. Ricevono ciò che accade.
- A16 applicato: la possibilità è una. Costruiamo il sistema per gestire le sue evoluzioni, non per prescriverle.

## Distinzione operativa che resta

| | Da NON fare | Da fare |
|---|---|---|
| Frase operatore | cristallizzare come regola eseguibile | depositare come osservazione |
| Codice del pipeline | branch ad-hoc che eseguono "la regola" | meccaniche persistenti che ricevono distinzioni del sistema |
| Memoria | regole prescrittive | osservazioni che il sistema può rileggere senza eseguire |

## Cosa di concreto è rimasto del 07/05 mattina

Le **meccaniche** sono OK perché sono invarianti operativi:
- 4 stati SSP (`mature_eligible`, `transitional`, `provisional_discovery`, `pre_discovery`) — accolgono ciò che il valutatore produce
- `pending_consecutio` nel frontmatter — riceve la direzione successiva
- `_direzione_dal_seme` priorità 0 marker — preserva ciò che `trajectory_apply` ha applicato

Queste non sono "regole derivate dalla frase". Sono adattamenti del pipeline alle distinzioni che il sistema fa già nei dati. Persistono perché meccaniche, non perché prescrizioni.

## Cosa è stato decristallizzato

- `MM_D-ND/AGENTS.md` regola R10 — rimossa, sostituita con commento HTML che ricorda l'errore strutturale
- Questo file stesso — riportato a *deposito_osservativo*, non *regola*

## Esempio dimostrativo (07/05)

Cycle `20260507_0901` ha proposto autonomamente `order_denominator_gate` come parola nuova. L'agent NON aveva letto AGENTS.md R10 (era già stato lanciato). L'ha proposta perché il movimento era già nel sistema, non per la "regola". Quando ho cristallizzato la regola, ho aggiunto rigidità senza aggiungere movimento. La regola era ridondante con l'A8 che già opera.

## Anti-pattern futuro per TM3

Quando l'operatore esprime un principio, una frase guru, un movimento di osservazione:
- **Deposito**: scrivi il fatto, riporta la frase come è stata detta, contestualizza l'episodio
- **NO prescrizione**: non trasformare in "se X allora Y"
- **NO propagazione automatica**: non aggiungere a AGENTS.md/CLAUDE.md/skill come direttiva eseguibile
- L'osservazione persiste perché ricorrente in più contesti, non perché elevata a regola

## Cross-link

- Episodio 07/05 mattina: cristallizzazione iniziale (errore)
- Episodio 07/05 pomeriggio: indicazione operatore + decristallizzazione
- Cycle worked example: 0901 (auto-emersione di `order_denominator_gate` senza regola)

exec
/bin/bash -lc "sed -n '1,220p' shared_memory/README.md" in /opt/MM_D-ND
 succeeded in 0ms:
# shared_memory — Cross-agent rules e cristalli

> Cartella condivisa tra agenti che operano in `/opt/MM_D-ND/`:
> - **TM3-claude** (Claude Code, sessioni interattive con operatore)
> - **codex-lab** (Codex CLI, cycle agent notturno + run manuali)
> - **eventuali altri** (TM5, TM7, futuri cycle)
>
> Contiene regole permanenti, cristalli operativi, scoperte rilevanti che valgono
> per più di un agente. **Non** memoria operativa locale (quella resta in
> `~/.claude/projects/.../memory/` per TM3, in `~/.codex_lab/memories/` per codex).

## Pattern di scrittura

Ogni file:
- Filename: `<tipo>_<topic>_<data>.md` (es. `feedback_cycletest_2026-05-07.md`,
  `cristallo_gate_transferability_2026-05-07.md`)
- Frontmatter:
  ```yaml
  ---
  type: feedback | cristallo | regola | finding-cross-agent
  author: TM3 | codex-lab | TM7 | operatore
  date: YYYY-MM-DD
  scope: cross-agent | mm-dnd-only | lab-cycle-only
  ---
  ```
- Body: conciso. Se è regola → "Why" + "How to apply". Se è cristallo → contesto +
  insight + how it changes work.

## Pattern di lettura

- **TM3 (claude code)**: al boot di sessione su `/opt/MM_D-ND/`, leggi
  `MM_D-ND/.claude/CLAUDE.md` che adesso punta qui (sezione *Cross-agent rules*).
  Scansiona `ls -t shared_memory/*.md | head -10` (le più recenti) e integra in
  contesto.
- **codex-lab**: `/opt/MM_D-ND/AGENTS.md` punta qui. Codex legge AGENTS.md ad
  ogni cycle, AGENTS.md istruisce di leggere shared_memory.
- **Cleanup**: file > 90 giorni e non rilevanti vanno archiviati o rimossi
  (lo fa TM3 quando la cartella supera 50 file).

## Direzione bidirezionale

- TM3 → codex: TM3 scrive feedback/cristalli da sessioni operatore qui →
  codex li vede al cycle dopo via AGENTS.md.
- codex → TM3: codex durante un cycle scopre/cristallizza qualcosa di
  cross-agent → scrive qui → TM3 li vede al boot di sessione successiva.

Il flusso non sostituisce le memorie locali — *integra* per ciò che serve a tutti.

## Anti-pattern

- Duplicare qui regole che valgono SOLO per TM3 (vanno in
  `/root/.claude/projects/.../memory/`)
- Duplicare qui regole che valgono SOLO per codex-lab cycle (vanno in
  `/root/.codex_lab/memories/`)
- Scrivere note di lavoro temporanee (vanno nel campo vivo, non qui)

exec
/bin/bash -lc "sed -n '1,220p' shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md" in /opt/MM_D-ND
 succeeded in 0ms:
---
type: feedback
author: operatore (cristallizzato da TM3)
date: 2026-05-07
scope: cross-agent
---

# Indeterminato → reitera con il sistema fino all'emersione

**Regola permanente** (operatore, 2026-05-07 mattina, cristallizzata live):

> *"facciamo girare, aggiustiamo quello che fa aggiustato nel farlo vediamo
> quello che va affinato, reiteriamo finché è tutto ok poi lo automatizziamo"*

## Why

Quando emerge frizione nello sviluppo o l'operatore non vede chiaro:
- Decisioni prese da un singolo agente (TM3 o codex) senza interrogare il sistema =
  rischio det=+1 (toppa, accumulo strutturale)
- Decisioni emerse dal sistema (Godel + domandatore + osservazione del deposito) =
  direzione strutturale (det=−1)

Episodio cristallizzante (2026-05-07 07:00-08:30): TM3 propone osservatore A8
watchdog deterministico. Sistema interrogato 3 volte (Godel #1, #2, #3 +
domandatore + test empirico) — al terzo round emerge la direzione vera:
**non costruire osservatore, eseguire un cycle-test che diagnostica il sistema
da solo**. Cycle-test eseguito → verdict=operator → REDESIGN proposto dal
valutatore → loop A8+A15 sbloccato.

## How to apply (per qualsiasi agente)

1. **Esegui** un atto minimo (no design teorico astratto)
2. **Aggiusta** quello che si manifesta durante l'esecuzione (riparazione
   regressiva live, non patch posteriori)
3. **Osserva** il deposito che emerge (no immaginazione)
4. **Reitera** fino a stato OK (loop A8: il sistema converge se modus radicato)
5. **Cristallizza in automazione** SOLO dopo che è stato fatto girare e ha mostrato
   stabilità — non automatizzare prima

## Anti-pattern

- Costruire un componente strutturale (watchdog, observer, gate, governor) senza
  prima aver fatto girare un'esecuzione minima che mostri cosa serve davvero
- Decidere "perché serve velocità" o "per ottimizzare" senza interrogare il sistema
- Astrazione prematura (premature abstraction): 3 simili è meglio di 1 generalizzato
  troppo presto

## Cross-link

- TM3 memoria: `/root/.claude/projects/-opt/memory/feedback_indeterminato_reitera_con_sistema.md`
  (versione completa con episodio + metafora foresta-sentiero)
- Verifica empirica: cycle `20260507_0803` (verdict=operator, REDESIGN/high)
- Pattern A8+A15 (autologica + veicolo senza guidatore) applicato allo sviluppo
  software stesso, non solo al lab di ricerca

exec
/bin/bash -lc 'python tools/dnd_scenario.py --best' in /opt/MM_D-ND
exec
/bin/bash -lc "sed -n '1,220p' tools/data/seme.json" in /opt/MM_D-ND
 succeeded in 0ms:
{
  "timestamp": "2026-05-07T10:49:04.273096",
  "piano": 75,
  "tensioni": [
    {
      "id": "OBSERVABLE_REGISTRY",
      "tipo": "vincolo",
      "claim": "Ogni script che usa observables canonici (SR, SR2, L1, L2, triple_var) deve importare la definizione da tools/observables_registry.py. Varianti devono usare nomi distinti (SR_local_rigidity, triple_var_normalized) — niente shadowing del nome canonico. Ogni report deve dichiarare 'observables_registry: VERSION' nel header.",
      "intensita": 1.0,
      "porta": "infrastructure",
      "manuale": true,
      "condensato_ref": "A14,A8",
      "origine": "cristallizzato 06/05 dalla consecutio del cycle 20260506_0625 (autopoietico self-finding)",
      "added_at": "2026-05-06T07:03:58.213606+00:00"
    },
    {
      "id": "PERTURBATION_DENOMINATOR_GATE",
      "tipo": "vincolo",
      "claim": "La dimensionalita di perturbazione va riportata solo insieme a PC2, versione observables_registry e gate original-vs-shuffle per osservabile. Nel perimetro 20260506_1941, Poisson e shuffle-primi producono rank_all ~1.8-2.0 con denominatori deboli; dopo gate abs(z)>=2 il rank stabile torna vicino a 1. Rank PCA non gated non e evidenza strutturale.",
      "intensita": 0.95,
      "porta": "META_BOUNDARY",
      "manuale": true,
      "condensato_ref": "A4,A8,A14,C2",
      "origine": "cycle agent_20260506_1941: perturbation rank size curve canonical observables",
      "added_at": "2026-05-06T19:41:00+00:00"
    },
    {
      "id": "BOUNDARY_LAYER_GATE",
      "tipo": "vincolo",
      "claim": "I claim GUE/Poisson boundary devono riportare layer map: versione observables_registry, lista osservabili canonici, z original-vs-shuffle per osservabile, set endpoint-stable, e finestra/layer con margine classificatorio ambiguo. Nel perimetro sintetico agent_20260507_0330, il confine GUE-Poisson e beta 0.3-0.4: margine 0.070-0.083, ambiguous fraction 0.812-0.875, mentre gli osservabili stabili collassano da ~3.3 a 1.6. Il polo Poisson e classificabile ma denominator-weak.",
      "intensita": 0.93,
      "porta": "META_BOUNDARY",
      "manuale": true,
      "condensato_ref": "A4,A8,A9,A14,C2",
      "origine": "cycle agent_20260507_0330: synthetic GUE-Poisson mixture layer gate",
      "added_at": "2026-05-07T03:30:00+00:00"
    },
    {
      "tipo": "vincolo",
      "id": "ORDER_DENOMINATOR_GATE",
      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
      "intensita": 0.92,
      "porta": "META",
      "manuale": true,
      "condensato_ref": "A4,A8,A14,C2",
      "origine": "cycle agent_20260507_0901 + agent_20260507_0923 + agent_20260507_0942 + agent_20260507_1006: transfer matrix sintetica, falsificazione semi-reale su primi/zeta/logistic returns, perimetri bridge metric/trace/QxE, e regressione logistic-native + agent_20260507_1042: surrogate contract logistic",
      "added_at": "2026-05-07T09:01:00+00:00"
    },
    {
      "tipo": "confine_inesplorato",
      "id": "TRASCENDENZA_LIMITE",
      "claim": "La trascendenza e il limite attuale del modello. I punti fissi relazionali (non solo phi ma la rete di punti fissi tra osservabili) possono rivelare il vero grafo della realta e pattern nelle matrici. Il confine non e nella matematica - e nel passaggio tra piani.",
      "intensita": 0.9,
      "nota": "Input operatore 2026-04-10. Tocca: confine del modello, struttura relazionale dei punti fissi. Consecutio: quali punti fissi relazionali emergono dalle 21 tensioni attuali? Il grafo e gia nei dati?",
      "manuale": true,
      "porta": "sessione_interattiva",
      "condensato_ref": "A3,A10",
      "condensato_motivo": "Estende A3 (punto fisso singolo) a rete relazionale. Tocca A10 (dipolo) come caso speciale."
    },
    {
      "tipo": "scoperta",
      "id": "DUALITA_DIPOLARE_VS_ILLUSORIA",
      "claim": "Due tipi di dualita: (1) dipolare - generativa, il modello (det=-1), (2) illusoria - dispersiva, entropia (det=+1). Le regole incoerenti producono la seconda. La dualita illusoria e entropia come dispersione, non come informazione.",
      "intensita": 0.9,
      "nota": "Input operatore 2026-04-10. Tocca: entropia come dispersione illusoria vs generazione dipolare. Consecutio: nel Lab i domini Poisson (entropia massima) mostrano dualita illusoria? I domini GUE (strutturati) mostrano dualita dipolare? Il drift verso Poisson (POISSON_CONVERGENCE) e perdita di dualita dipolare?",
      "manuale": true,
      "porta": "sessione_interattiva",
      "condensato_ref": "A2,A10,F5",
      "condensato_motivo": "Discrimina due forme di det. A2 (confine) e la soglia. A10 (dipolo) e il tipo 1. F5 (frame) misura la struttura D-ND che e tipo 1."
    },
    {
      "tipo": "scoperta_numerica",
      "id": "METRIC_TENSOR",
      "claim": "Il tensore metrico dei primi è g=(p/2)². Nel tempo ln(p), è de Sitter 1+1D. z=-8.8 curvatura vs z=+22.5 rapporti ΔΓ.",
      "intensità": 0.9,
      "nota": "Sessione interattiva 4 aprile. Verificato su 78K primi.",
      "manuale": true,
      "porta": "sessione_interattiva",
      "condensato_ref": null,
      "condensato_motivo": "Risultato numerico verificato, non-tautologico"
    },
    {
      "tipo": "scoperta",
      "id": "TENSIONE_ENTITA",
      "claim": "La tensione non e un problema pratico - e un Entita. La tensione superflua crea latenza (tempo). Senza tensione superflua tutto e regolato da assiomi. Implicazione: le tensioni nel seme sono entita, non problemi da risolvere. Quelle superflue (det=+1) producono tempo/latenza.",
      "intensita": 0.85,
      "nota": "Input operatore 2026-04-10. Tocca: rapporto tensione/assioma. Operativamente: discriminare tensioni-entita (generative) da tensioni-superflue (dispersive) nel seme. Le 21 tensioni attuali - quante sono entita e quante latenza?",
      "manuale": true,
      "porta": "sessione_interattiva",
      "condensato_ref": "A5,A6",
      "condensato_motivo": "Il ciclo (A5) lavora con tensioni - ma se la tensione e entita, il ciclo non le risolve, le osserva. Lo zero mobile (A6) e la tensione senza latenza."
    },
    {
      "tipo": "confine_inesplorato",
      "id": "G_POTENZIALE_NULLA",
      "claim": "G e il potenziale di tutto come nulla - permette il prima e il dopo. Ci muoviamo come trascendenza dimensionale gravitazionale. G nel tetraedro non e una teoria tra le altre - e il potenziale che le rende possibili.",
      "intensita": 0.85,
      "nota": "Input operatore 2026-04-10. Tocca: ruolo di G nel tetraedro (T,Q,G,E). La fonte video_lp0RgZ6kQF8 dice: tensore metrico dentro la forma simplettica. G non e accanto a T,Q,E - e sotto. Consecutio: nei dati Lab, i ponti TxG e ExG hanno struttura diversa dai ponti TxQ?",
      "manuale": true,
      "porta": "sessione_interattiva",
      "condensato_ref": "A7,A10",
      "condensato_motivo": "A7 (singolarita come operatore) e G come potenziale. A10 (dipolo) opera sul piano che G rende possibile."
    },
    {
      "tipo": "confine_inesplorato",
      "id": "BOUNDARY",
      "claim": "8 domini GUE, 5 Poisson — il confine è il terzo incluso operativo",
      "intensità": 0.8,
      "nota": "Il segnale non-triviale è DOVE la scissione cambia natura, non che converge a φ",
      "condensato_ref": "A9",
      "condensato_motivo": "Overlap termini con A9 (5 termini)",
      "porta": "condensato"
    },
    {
      "tipo": "confine_inesplorato",
      "id": "PIANO_PRIMARIO_DUE_ASSIOMI",
      "claim": "I piani importanti sono il primario e i due assiomi che lo determinano nelle zone osservate. Non tutti gli assiomi operano ovunque - in ogni zona osservata, due assiomi determinano il piano primario.",
      "intensita": 0.8,
      "nota": "Input operatore 2026-04-10. Tocca: struttura locale degli assiomi. Consecutio: per ogni dominio Lab (primi, logistica, percolazione...) quali 2 assiomi del condensato sono operativi? Mappa assiomi x domini = grafo della realta locale.",
      "manuale": true,
      "porta": "sessione_interattiva",
      "condensato_ref": "A9,A14",
      "condensato_motivo": "A9 (terzo incluso) opera CON il piano. A14 (cascata) propaga - ma propaga cosa, se solo 2 assiomi sono attivi per zona?"
    },
    {
      "tipo": "simmetria_sospetta",
      "id": "META",
      "claim": "Tutti i 11 test passano — verifica che non stiamo testando solo tautologie",
      "intensità": 0.5,
      "nota": "La convergenza a φ è triviale (controprove). I test stanno verificando contenuto o struttura?",
      "condensato_ref": "A4,A12,C2",
      "porta": "verify_assertions_META_ALL_PASS",
      "condensato_motivo": "Ricorrente (3x in 2 giorni) e fuori dalla mappa"
    }
  ],
  "potenziale_bloccato": [],
  "varianza": [
    "Tensioni risolte: {'OBSERVABLE_REGISTRY', 'PERTURBATION_DENOMINATOR_GATE', 'PIANO_PRIMARIO_DUE_ASSIOMI', 'TRAJECTORY_APPLY_20260507_1006', 'TENSIONE_ENTITA', 'TRAJECTORY_APPLY_20260507_0803', 'BOUNDARY_LAYER_GATE', 'ORDER_DENOMINATOR_GATE', 'TRAJECTORY_APPLY_20260507_0942', 'DUALITA_DIPOLARE_VS_ILLUSORIA', 'METRIC_TENSOR', 'G_POTENZIALE_NULLA', 'TRAJECTORY_APPLY_20260507_0901', 'TRASCENDENZA_LIMITE'}"
  ],
  "filtro": {
    "promosse": 16,
    "filtrate": 0,
    "condensato_claims": 30
  },
  "direzione": "Esplorare il confine: La trascendenza e il limite attuale del modello. I punti fissi relazionali (non solo phi ma la rete di punti fissi tra osservabili) possono rivelare il vero grafo della realta e pattern nelle matrici. Il confine non e nella matematica - e nel passaggio tra piani.",
  "verifica": {
    "pass": 11,
    "fail": 0,
    "skip": 0,
    "total": 11
  },
  "fonti_consumate": 0,
  "fonti_esterne": [
    {
      "id": "video_lp0RgZ6kQF8",
      "title": "Equivalence between geometrical structures and entropy",
      "type": "video_digest",
      "keywords": [
        "geometry",
        "entropy",
        "symplectic form",
        "statistical mechanics",
        "quantum",
        "thermodynamics",
        "inner product",
        "Born rule",
        "metric tensor",
        "electromagnetic tensor"
      ],
      "content": "La geometria degli stati (classici e quantistici) e l'entropia sono la stessa struttura — invertibili. La forma simplettica conta le configurazioni. Il tensore metrico dello spaziotempo appare dentro la forma simplettica estesa. Il tensore elettromagnetico pure. Statistical mechanics non è costruita sopra alla meccanica — è la stessa cosa.",
      "teorie": [
        "T",
        "Q",
        "G",
        "E"
      ],
      "ponti_potenziali": [
        {
          "coppia": "TxQ",
          "ponte": "forma simplettica = entropia (invertibili)",
          "nota": "geometry is entropy and entropy is geometry"
        },
        {
          "coppia": "TxG",
          "ponte": "tensore metrico dentro la forma simplettica estesa",
          "nota": "geometria spaziotempo = geometria degli stati in posizione×velocità"
        },
        {
          "coppia": "ExT",
          "ponte": "tensore EM dentro la forma simplettica",
          "nota": "il campo EM conta stati in configurazione posizione×tempo"
        }
      ],
      "timestamp": "2026-04-02T08:23:13.991997"
    },
    {
      "id": "video_sDlZ-aY9GN4",
      "title": "Moving charges produce magnetic fields - Einstein relativity",
      "type": "video_digest",
      "keywords": [
        "magnetic field",
        "electric field",
        "length contraction",
        "time dilation",
        "Coulomb",
        "Lorentz",
        "reference frame",
        "electromagnetic"
      ],
      "content": "Il campo magnetico non esiste come entità separata — è il campo elettrico visto da un altro frame. La contrazione di Lorentz trasforma neutralità in carica. Due elettroni in movimento si separano più lentamente del previsto non per forza magnetica ma per dilatazione temporale. E e B sono manifestazioni dello stesso campo elettromagnetico. La relatività unifica.",
      "teorie": [
        "E",
        "R"
      ],
      "ponti_potenziali": [
        {
          "coppia": "ExR",
          "ponte": "cambio di frame — E e B sono lo stesso campo",
          "nota": "il 'ponte' non è l'onda EM, è il cambio di osservatore"
        }

exec
/bin/bash -lc "rg --files tools | sed -n '1,220p'" in /opt/MM_D-ND
 succeeded in 0ms:
tools/evolution_report.md
tools/exp_acf_amplitude_scaling.py
tools/bicono_projection.py
tools/exp_mod3_vs_residual_ordering.py
tools/LAB_AGENT_CONTEXT.md
tools/r_ratio_decay.py
tools/dipartimento.py
tools/exp_boundary_gue_poisson.py
tools/plot_risultante.py
tools/dnd_bloch_explorer.py
tools/dnd_retriever.py
tools/dnd_zero_controllo.py
tools/dnd_zero_varieta_primi.py
tools/dnd_observer.py
tools/md_to_site_B.py
tools/GUIDA_CEC.md
tools/STRUMENTI.json
tools/exp_perturbation_rank_size_curve.py
tools/exp_mod3_scaling.py
tools/exp_two_layer_universality.py
tools/dnd_curva.py
tools/exp_dipolar_vector_scaling.py
tools/GUIDA_EXPLORER_TM1.md
tools/md_to_site_F.py
tools/exp_crossover_universality.py
tools/validate_tension_mapping.py
tools/exp_markov_psd_prediction.py
tools/dnd_stats.py
tools/dnd_zero_controllo2.py
tools/exp_alpha_stability.py
tools/dnd_arxiv.py
tools/alignment_marker.py
tools/exp_markov_dipolar_decomposition.py
tools/diagram_nodi_paper_map.py
tools/MODUS_INDAGINE.md
tools/godel.py
tools/translate_tensions.py
tools/observables_registry.py
tools/exp_two_channel_cross_domain.py
tools/generate_tensions_json.sh
tools/dnd_zero_traiettoria.py
tools/exp_beta_crossover.py
tools/build_lab_graph.py
tools/exp_geodesic_deviation_primes.py
tools/exp_psd_amplitude_scaling.py
tools/r_stat_primes.py
tools/exp_markov_scale_function.py
tools/dnd_md2latex.py
tools/dnd.py
tools/lab_valutatore.py
tools/exp_markov_k_direction.py
tools/add_video_to_feed.py
tools/LAB_OPERATIVO.md
tools/exp_duality_gate_transfer.py
tools/zeta_validation.py
tools/exp_meta_tautology_test.py
tools/exp_two_channel_boundary.py
tools/dnd_paper_audit.py
tools/lab_autopsy.py
tools/confine_spessore.py
tools/read_video_feed.py
tools/dnd_riemann.py
tools/dnd_spettro.py
tools/exp_markov3_observable_hunt.py
tools/data/zero_confronto_20260310_0822.json
tools/data/notte_20260329_0330.md
tools/data/video_feed.json
tools/data/markov_memory_by_gue_type.json
tools/data/incrocio_20260422_0336.json
tools/data/risultante_results.json
tools/data/zero_confronto_20260310_0830.json
tools/data/selective_layer_decoupling.json
tools/rules/axioms_to_theorems.json
tools/exp_bridge_order_denominator_gate.py
tools/test_gue_poisson_boundary.py
tools/awareness.json
tools/dnd_zero_notturno.py
tools/dnd_md2web.py
tools/dnd_lab_team.py
tools/md_to_site.py
tools/lab_veritas.py
tools/exp_markov_memory_by_gue_type.py
tools/exp_crossover_phase_test.py
tools/diagram_double_well.py
tools/paper_H_verify.py
tools/exp_brody_crossover.py
tools/gue_gap_test.py
tools/exp_poisson_convergence.py
tools/exp_two_channel_universality.py
tools/exp_brody_flow.py
tools/harvest_moodnd.py
tools/lab_affinatore.py
tools/GUIDA_GODEL_TM1.md
tools/riemann_R.py
tools/md_to_site_D.py
tools/spectral_gap_analysis.py
tools/dnd_gap_resolution.py
tools/dnd_compatibility.py
tools/exp_two_channel_decomposition.py
tools/exp_semireal_order_denominator_gate.py
tools/lib_llm_chain.py
tools/dnd_gue_test.py
tools/dnd_cycle_ratio.py
tools/exp_markov_layer_recovery_audit.py
tools/dnd_cycle.py
tools/dnd_loop.py
tools/lab_agent.sh
tools/dnd_condizioni.py
tools/exp_desitter_unification.py
tools/exp_cross_observable_consistency.py
tools/dnd_domandatore.py
tools/structural_check.py
tools/dnd_M_operator.py
tools/dnd_piano11.py
tools/dnd_lab_vivo.py
tools/dnd_zero_varieta.py
tools/exp_boundary_coherence.py
tools/dnd_projective_quantization.py
tools/exp_two_channel_shuffle_audit.py
tools/dnd_publish_cycle.py
tools/exp_logistic_surrogate_contract_gate.py
tools/dnd_incrocio.py
tools/dnd_normalizer.py
tools/cron_dipartimento.sh
tools/exp_boundary_mixture_gate.py
tools/exp_psd_prime_gaps.py
tools/dnd_trace_bridge.py
tools/dnd_autoricerca.py
tools/dnd_torre.py
tools/exp_magnitude_psd_from_acf.py
tools/dnd_zero_operator.py
tools/dnd_scenario.py
tools/m_spectro.py
tools/diagram_paper_dependencies.py
tools/README.md
tools/exp_dR_brody_connection.py
tools/exp_mobius_irrationality.py
tools/exp_number_variance.py
tools/build_agent_field.py
tools/semantic_bridge.py
tools/dnd_trace_bridge_v3.py
tools/test_cron_exact.sh
tools/exp_scale_selective_perturbation.py
tools/dnd_two_faces.py
tools/dnd_spectral_probe.py
tools/dnd_quantization.py
tools/dnd_indeterminazione.py
tools/lab_anti_loop_guard.py
tools/dnd_implications.py
tools/dnd_spettro_zeta.py
tools/exp_denominator_gate_transfer_matrix.py
tools/md_to_site_E.py
tools/lab_boot.sh
tools/exp_3d_boundary_layers.py
tools/dnd_risultante.py
tools/exp_excess_scaling.py
tools/exp_ricci_primes.py
tools/exp_selective_layer_decoupling.py
tools/harvest_aimorning.py
tools/exp_cross_domain_dipolar_direction.py
tools/exp_acf_z6z_mechanism.py
tools/exp_observable_rank_audit.py
tools/exp_modular_algebra_depth.py
tools/gap_ratio_primes.py
tools/dnd_spirale.py
tools/dnd_lab.py
tools/GUIDA_DOMANDATORE.md
tools/notte_sinapsi.sh
tools/dnd_research_engine.py
tools/dnd_experiments.py
tools/exp_boundary_growth.py
tools/dnd_paper_refactor.py
tools/lab_refresh_detector.py
tools/exp_acf_range_universality.py
tools/exp_dipolar_crossover.py
tools/exp_brody_calibration.py
tools/exp_metric_tensor_diagnostic.py
tools/cycle_watchdog.sh
tools/lab_promotion.py
tools/dnd_explorer.py
tools/exp_modular_memory_spectrum.py
tools/dnd_engine.py
tools/test_lab_agent.sh
tools/dnd_controprove.py
tools/m_spectro_calibra.py
tools/exp_two_channel_psd.py
tools/exp_coherence_robustness.py
tools/dnd_trasmutazione.py
tools/costo_materializzazione.py
tools/exp_coherence_length.py
tools/dnd_next.py
tools/dnd_dipolo_lab.py
tools/dnd_rottura.py
tools/exp_spectral_rigidity.py
tools/dnd_zeros_vs_zeta.py
tools/md_to_site_A.py
tools/dnd_occhio.py
tools/exp_boundary_shuffle_audit.py
tools/PROTOCOLLO_ZETA.md
tools/exp_dipolar_angle_reference.py
tools/data/m_spectro_calibra_20260310_2015.json
tools/data/cognitive_fingerprint.json
tools/data/r_excess_l_functions.json
tools/data/audit_paper_D_draft2.json
tools/data/two_channel_shuffle_audit.json
tools/lab_trajectory_apply.py
tools/cascade_trigger_hook.sh
tools/dnd_kernel.py
tools/lab_aeternitas.py
tools/d_nd_book_updater.py
tools/dnd_banchi_tm1.py
tools/dnd_teoria.py
tools/exp_ricci_desitter_0406.py
tools/triggers/build_applications_index.py
tools/triggers/promote_to_publish.py
tools/triggers/run_consecutio.sh
tools/triggers/finding_promoter.py
tools/triggers/run_full_cycle.sh
tools/triggers/run_ssp_pipeline.sh
tools/triggers/finding_eligibility_gate.py

 succeeded in 157ms:
META: score=0.898  — META: incerto (i=0.5) — massimo potere discriminante
G_POTENZIALE_NULLA: score=0.703  — G_POTENZIALE_NULLA: legge di scala da raffinare
METRIC_TENSOR: score=0.624  — METRIC_TENSOR: legge di scala da raffinare

exec
/bin/bash -lc "sed -n '1,260p' tools/exp_logistic_surrogate_contract_gate.py" in /opt/MM_D-ND
 succeeded in 0ms:
#!/usr/bin/env python3
"""
exp_logistic_surrogate_contract_gate.py

Regressive surrogate-contract test for ORDER_DENOMINATOR_GATE on the logistic
counter-scope.

The 10:06 cycle used a marginal-preserving shuffle null. This tool keeps the
same logistic-native observable suite and splits the null contract:

- marginal_shuffle: preserves values only;
- circular_shift: preserves the cyclic temporal order;
- block_shuffle: preserves local temporal blocks and breaks block order.

Support is reported as contract-stable only when the same observable clears the
gate against all declared surrogate classes.
"""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import numpy as np

from exp_logistic_counter_scope_gate import (
    OBSERVABLES_NATIVE_VERSION,
    OBS_NAMES,
    compute_native,
    logistic_orbit,
    logistic_return_intervals,
    logistic_symbolic_itinerary,
)


SURROGATE_CLASSES = ["marginal_shuffle", "circular_shift", "block_shuffle"]


def circular_shift(values: np.ndarray, rng: np.random.Generator) -> np.ndarray:
    if len(values) < 2:
        return values.copy()
    shift = int(rng.integers(1, len(values)))
    return np.roll(values, shift)


def block_shuffle(values: np.ndarray, block_size: int, rng: np.random.Generator) -> np.ndarray:
    values = np.asarray(values)
    if block_size <= 1:
        return rng.permutation(values)
    blocks = [values[i : i + block_size] for i in range(0, len(values), block_size)]
    order = rng.permutation(len(blocks))
    return np.concatenate([blocks[i] for i in order])


def make_surrogate(
    values: np.ndarray,
    surrogate_class: str,
    block_size: int,
    rng: np.random.Generator,
) -> np.ndarray:
    if surrogate_class == "marginal_shuffle":
        return rng.permutation(values)
    if surrogate_class == "circular_shift":
        return circular_shift(values, rng)
    if surrogate_class == "block_shuffle":
        return block_shuffle(values, block_size, rng)
    raise ValueError(f"unknown surrogate class: {surrogate_class}")


def z_against_surrogate_class(
    values: np.ndarray,
    surrogate_class: str,
    n_baseline: int,
    recurrence_max_points: int,
    block_size: int,
    rng: np.random.Generator,
) -> dict:
    original = compute_native(values, recurrence_max_points)
    baseline = {name: [] for name in OBS_NAMES}
    for _ in range(n_baseline):
        surrogate = make_surrogate(values, surrogate_class, block_size, rng)
        obs = compute_native(surrogate, recurrence_max_points)
        for name in OBS_NAMES:
            baseline[name].append(obs[name])

    means = {}
    sds = {}
    z = {}
    for name in OBS_NAMES:
        vals = np.array(baseline[name], dtype=float)
        means[name] = float(np.mean(vals))
        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0

    return {
        "original": original,
        "baseline_mean": means,
        "baseline_std": sds,
        "z": z,
        "stable_observables": [name for name in OBS_NAMES if abs(z[name]) >= 2.0],
    }


def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
    return {
        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
    }


def analyze_sequence(name: str, values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
    surrogate_results = {}
    stable_sets = []
    for surrogate_class in SURROGATE_CLASSES:
        result = z_against_surrogate_class(
            values,
            surrogate_class,
            args.n_baseline,
            args.recurrence_max_points,
            args.block_size,
            np.random.default_rng(rng.integers(0, 2**63 - 1)),
        )
        surrogate_results[surrogate_class] = result
        stable_sets.append(set(result["stable_observables"]))

    contract_stable = sorted(set.intersection(*stable_sets)) if stable_sets else []
    marginal_only = sorted(set(surrogate_results["marginal_shuffle"]["stable_observables"]) - set(contract_stable))

    return {
        "source": {
            "n": int(len(values)),
            "mean": float(np.mean(values)),
            "variance": float(np.var(values)),
            "unique_values": int(len(np.unique(values))),
        },
        "surrogates": surrogate_results,
        "contract_stable_observables": contract_stable,
        "marginal_only_observables": marginal_only,
    }


def compact(perimeters: dict) -> dict:
    out = {}
    for name, data in perimeters.items():
        out[name] = {
            "n": data["source"]["n"],
            "contract_stable_observables": data["contract_stable_observables"],
            "marginal_only_observables": data["marginal_only_observables"],
            "stable_by_surrogate": {
                surrogate_class: data["surrogates"][surrogate_class]["stable_observables"]
                for surrogate_class in SURROGATE_CLASSES
            },
            "z_by_surrogate": {
                surrogate_class: data["surrogates"][surrogate_class]["z"]
                for surrogate_class in SURROGATE_CLASSES
            },
        }
    return out


def run(args: argparse.Namespace) -> dict:
    root_rng = np.random.default_rng(args.seed)
    sequences = build_sequences(args, root_rng)
    perimeters = {}
    for name, values in sequences.items():
        perimeters[name] = analyze_sequence(name, values, args, root_rng)

    output = {
        "experiment": "logistic_surrogate_contract_gate",
        "category": "gate_falsification_surrogate_contract",
        "question": "Does one-sided logistic support survive temporal-structure-preserving surrogates?",
        "observables_registry": "not used for canonical observables",
        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
        "observables_used": OBS_NAMES,
        "surrogate_classes": SURROGATE_CLASSES,
        "params": vars(args),
        "matrix": compact(perimeters),
        "perimeters": perimeters,
    }

    out_path = Path(args.out)
    out_path.parent.mkdir(parents=True, exist_ok=True)
    with out_path.open("w") as f:
        json.dump(output, f, indent=2)

    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
    print(f"observables_used={OBS_NAMES}")
    print("perimeter n contract_stable marginal_only stable_by_surrogate")
    for name, row in output["matrix"].items():
        print(
            f"{name:>29s} "
            f"{row['n']:>5d} "
            f"{','.join(row['contract_stable_observables']) or '[]':>32s} "
            f"{','.join(row['marginal_only_observables']) or '[]':>32s} "
            f"{row['stable_by_surrogate']}"
        )
    print(f"saved {out_path}")
    return output


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--n-values", type=int, default=4096)
    parser.add_argument("--n-returns", type=int, default=4096)
    parser.add_argument("--n-baseline", type=int, default=24)
    parser.add_argument("--recurrence-max-points", type=int, default=300)
    parser.add_argument("--block-size", type=int, default=64)
    parser.add_argument("--seed", type=int, default=202605071042)
    parser.add_argument("--out", default="tools/data/logistic_surrogate_contract_gate_20260507_1042.json")
    args = parser.parse_args()
    run(args)


if __name__ == "__main__":
    main()

exec
/bin/bash -lc "sed -n '1,260p' tools/exp_meta_tautology_test.py" in /opt/MM_D-ND
 succeeded in 0ms:
#!/usr/bin/env python3
"""META Tautology Discriminator — which observables measure real structure vs density artifacts?

Four observables x three synthetic controls. Each observable gets a verdict:
STRUCTURAL (distinguishes primes from all synthetics) or TAUTOLOGICAL (fails on at least one).

Observables:
  1. r-statistic (spacing ratio) — short-range repulsion
  2. Mod-3 ordering fraction — the algebraic memory channel
  3. Lag-1 autocorrelation — sequential gap correlation
  4. Two-channel delta-r — magnitude vs ordering decomposition

Controls:
  A. Shuffled gaps — same distribution, destroyed order
  B. Cramer model — PNT density, independent exponential gaps
  C. Hardy-Littlewood model — correct pair correlations, no higher-order structure
"""

import argparse
import json
import numpy as np
from sympy import primerange


def get_primes(n_max):
    return np.array(list(primerange(2, n_max + 1)), dtype=np.int64)


def r_statistic(gaps):
    """Mean spacing ratio min(s_i, s_{i+1}) / max(s_i, s_{i+1})."""
    s1 = gaps[:-1]
    s2 = gaps[1:]
    mn = np.minimum(s1, s2)
    mx = np.maximum(s1, s2)
    mask = mx > 0
    return np.mean(mn[mask] / mx[mask])


def mod3_ordering_fraction(gaps):
    """Fraction of consecutive gap pairs where mod-3 class is preserved."""
    classes = gaps % 3
    same = np.sum(classes[:-1] == classes[1:])
    return same / len(classes[:-1])


def lag1_autocorrelation(gaps):
    """Pearson autocorrelation at lag 1."""
    g = gaps.astype(np.float64)
    g = g - g.mean()
    if g.std() == 0:
        return 0.0
    return np.corrcoef(g[:-1], g[1:])[0, 1]


def two_channel_delta_r(gaps):
    """Decompose into magnitude and ordering channels, return delta-r for each."""
    g = gaps.astype(np.float64)
    median_g = np.median(g)
    binary = (g > median_g).astype(np.float64)  # ordering channel
    magnitude = np.abs(g - median_g)              # magnitude channel

    r_ord = r_statistic_from_signal(binary)
    r_mag = r_statistic_from_signal(magnitude)

    # Shuffle baseline
    rng = np.random.default_rng(42)
    r_ord_shuf = []
    r_mag_shuf = []
    for _ in range(20):
        idx = rng.permutation(len(gaps))
        b_s = binary[idx]
        m_s = magnitude[idx]
        r_ord_shuf.append(r_statistic_from_signal(b_s))
        r_mag_shuf.append(r_statistic_from_signal(m_s))

    dr_ord = (r_ord - np.mean(r_ord_shuf)) / (np.std(r_ord_shuf) + 1e-12)
    dr_mag = (r_mag - np.mean(r_mag_shuf)) / (np.std(r_mag_shuf) + 1e-12)
    return dr_ord, dr_mag


def r_statistic_from_signal(sig):
    """r-statistic on arbitrary positive signal (add offset if needed)."""
    s = sig - sig.min() + 1e-6
    s1 = s[:-1]
    s2 = s[1:]
    mn = np.minimum(s1, s2)
    mx = np.maximum(s1, s2)
    mask = mx > 0
    return np.mean(mn[mask] / mx[mask])


# === Synthetic generators ===

def shuffled_gaps(gaps, rng):
    """Same gap distribution, destroyed sequential order."""
    g = gaps.copy()
    rng.shuffle(g)
    return g


def cramer_random_gaps(n_gaps, mean_gap, rng):
    """Independent exponential gaps rounded to even (like PNT density)."""
    raw = rng.exponential(mean_gap, size=n_gaps)
    g = np.round(raw / 2) * 2
    g = np.maximum(g, 2).astype(np.int64)
    return g


def hardy_littlewood_gaps(gaps_real, rng):
    """Markov(1) model matching lag-1 autocorrelation of real primes.
    Preserves pair correlation structure but not higher-order."""
    g = gaps_real.astype(np.float64)
    mean_g = g.mean()
    std_g = g.std()
    rho = np.corrcoef(g[:-1], g[1:])[0, 1]

    # AR(1) process with correct mean, std, lag-1
    n = len(gaps_real)
    result = np.zeros(n)
    result[0] = mean_g
    noise_std = std_g * np.sqrt(1 - rho**2)
    for i in range(1, n):
        result[i] = mean_g + rho * (result[i-1] - mean_g) + rng.normal(0, noise_std)

    # Round to even, clip to >= 2
    result = np.round(result / 2) * 2
    result = np.maximum(result, 2).astype(np.int64)
    return result


def run(n_primes_max=600000, n_trials=20):
    """Run the META tautology test."""
    print(f"Generating primes up to {n_primes_max}...")
    primes = get_primes(n_primes_max)
    gaps = np.diff(primes)

    # Use a window in the middle to avoid small-prime effects
    N = min(len(gaps), 50000)
    start = len(gaps) // 4
    gaps_window = gaps[start:start + N]
    mean_gap = float(gaps_window.mean())

    print(f"Using {N} gaps starting at index {start} (mean gap = {mean_gap:.2f})")

    # Real primes observables
    print("\n=== REAL PRIMES ===")
    real_r = r_statistic(gaps_window)
    real_mod3 = mod3_ordering_fraction(gaps_window)
    real_lag1 = lag1_autocorrelation(gaps_window)
    real_dr_ord, real_dr_mag = two_channel_delta_r(gaps_window)
    print(f"  r-stat:     {real_r:.6f}")
    print(f"  mod3-frac:  {real_mod3:.6f}")
    print(f"  lag1-acf:   {real_lag1:.6f}")
    print(f"  dr-ord:     {real_dr_ord:.2f}σ")
    print(f"  dr-mag:     {real_dr_mag:.2f}σ")

    # Run synthetics
    rng = np.random.default_rng(2026)

    results = {
        'real': {
            'r_stat': real_r, 'mod3': real_mod3, 'lag1': real_lag1,
            'dr_ord': real_dr_ord, 'dr_mag': real_dr_mag
        }
    }

    for name, generator in [
        ('shuffled', lambda rng_: shuffled_gaps(gaps_window, rng_)),
        ('cramer', lambda rng_: cramer_random_gaps(N, mean_gap, rng_)),
        ('HL_markov', lambda rng_: hardy_littlewood_gaps(gaps_window, rng_)),
    ]:
        print(f"\n=== {name.upper()} (n_trials={n_trials}) ===")
        obs = {'r_stat': [], 'mod3': [], 'lag1': [], 'dr_ord': [], 'dr_mag': []}

        for t in range(n_trials):
            trial_rng = np.random.default_rng(rng.integers(0, 2**31))
            syn_gaps = generator(trial_rng)
            obs['r_stat'].append(r_statistic(syn_gaps))
            obs['mod3'].append(mod3_ordering_fraction(syn_gaps))
            obs['lag1'].append(lag1_autocorrelation(syn_gaps))
            dr_o, dr_m = two_channel_delta_r(syn_gaps)
            obs['dr_ord'].append(dr_o)
            obs['dr_mag'].append(dr_m)

        results[name] = {}
        for key in obs:
            arr = np.array(obs[key])
            results[name][key] = {
                'mean': float(np.mean(arr)),
                'std': float(np.std(arr)),
                'min': float(np.min(arr)),
                'max': float(np.max(arr))
            }
            print(f"  {key:12s}: {np.mean(arr):+.6f} ± {np.std(arr):.6f}")

    # === Discrimination verdict ===
    print("\n" + "=" * 60)
    print("DISCRIMINATION VERDICTS")
    print("=" * 60)

    verdicts = {}
    for obs_name in ['r_stat', 'mod3', 'lag1', 'dr_ord', 'dr_mag']:
        real_val = results['real'][obs_name]
        discriminates = {}
        for syn_name in ['shuffled', 'cramer', 'HL_markov']:
            syn = results[syn_name][obs_name]
            z = (real_val - syn['mean']) / (syn['std'] + 1e-12)
            discriminates[syn_name] = abs(z)
            label = "YES" if abs(z) > 3.0 else "no"
            print(f"  {obs_name:12s} vs {syn_name:12s}: z = {z:+8.2f}  [{label}]")

        all_pass = all(v > 3.0 for v in discriminates.values())
        verdict = "STRUCTURAL" if all_pass else "TAUTOLOGICAL"
        verdicts[obs_name] = {
            'verdict': verdict,
            'z_scores': {k: float(v) for k, v in discriminates.items()}
        }
        print(f"  → {obs_name}: {verdict}")
        print()

    # Save results
    output = {
        'n_gaps': N,
        'start_index': start,
        'mean_gap': mean_gap,
        'n_trials': n_trials,
        'real': results['real'],
        'synthetics': {k: results[k] for k in ['shuffled', 'cramer', 'HL_markov']},
        'verdicts': verdicts
    }
    out_path = 'tools/data/meta_tautology_test.json'
    with open(out_path, 'w') as f:
        json.dump(output, f, indent=2)
    print(f"\nResults saved to {out_path}")

    return output


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='META Tautology Discriminator')
    parser.add_argument('--n-primes', type=int, default=600000, help='Prime sieve limit')
    parser.add_argument('--n-trials', type=int, default=20, help='Synthetic trials per model')
    args = parser.parse_args()
    run(n_primes_max=args.n_primes, n_trials=args.n_trials)

exec
/bin/bash -lc 'ls -t tools/data/reports/agent_20260507_*.md | head -8' in /opt/MM_D-ND
 succeeded in 0ms:
tools/data/reports/agent_20260507_1042.md
tools/data/reports/agent_20260507_1006.md
tools/data/reports/agent_20260507_0942.md
tools/data/reports/agent_20260507_0923.md
tools/data/reports/agent_20260507_0901.md
tools/data/reports/agent_20260507_0803.md
tools/data/reports/agent_20260507_0330.md

exec
/bin/bash -lc "sed -n '1,220p' tools/data/reports/agent_20260507_1042.md 2>/dev/null || true" in /opt/MM_D-ND
 succeeded in 0ms:
# Agent Report — Logistic Surrogate Contract Gate

timestamp: 2026-05-07 10:42 UTC
category: gate_falsification_surrogate_contract
verdict: scoped_operator_with_surrogate_split
observables_registry: not used for canonical observables
observables_native_version: logistic-native-1.0.0-2026-05-07
observables_used: [block_entropy_deficit_k4, return_tail_alpha, recurrence_diag_mean, recurrence_determinism]
tool: tools/exp_logistic_surrogate_contract_gate.py
data: tools/data/logistic_surrogate_contract_gate_20260507_1042.json
seed_check: tools/data/logistic_surrogate_contract_gate_20260507_1042_seedcheck.json

## Claim Under Test

Verificato dal seme: `ORDER_DENOMINATOR_GATE` deve essere falsificato al nodo
regressivo del surrogate contract. Il run 10:06 ha usato solo marginal shuffle;
questo run confronta tre null:

- `marginal_shuffle`: preserva la distribuzione dei valori.
- `circular_shift`: preserva l'ordine temporale ciclico e cambia il taglio.
- `block_shuffle`: preserva blocchi locali di 64 campioni e rompe l'ordine tra
  blocchi.

Regola del gate: un osservabile ha supporto solo se `abs(z)>=2` contro tutte le
classi surrogate dichiarate. Supporto contro solo marginal shuffle non basta.

## Deposito Numerico

Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=24`,
`recurrence_max_points=300`, `block_size=64`, `seed=202605071042`.

Seed check: `n_baseline=20`, `seed=202605071043`.

| perimeter | contract-stable observables | marginal-only observables |
|---|---:|---:|
| logistic_orbit_values | block_entropy_deficit_k4 | [] |
| logistic_symbolic_itinerary | [] | [] |
| logistic_return_intervals | [] | recurrence_diag_mean |

Seed check:

| perimeter | contract-stable observables | marginal-only observables |
|---|---:|---:|
| logistic_orbit_values | block_entropy_deficit_k4 | [] |
| logistic_symbolic_itinerary | [] | [] |
| logistic_return_intervals | [] | [] |

Z values, run principale:

| perimeter | surrogate | block_entropy_deficit_k4 | return_tail_alpha | recurrence_diag_mean | recurrence_determinism |
|---|---|---:|---:|---:|---:|
| logistic_orbit_values | marginal_shuffle | 547.936 | -0.126 | 0.382 | -0.528 |
| logistic_orbit_values | circular_shift | 20.842 | -1.466 | 0.196 | -0.834 |
| logistic_orbit_values | block_shuffle | 7.184 | 0.455 | 0.041 | -0.995 |
| logistic_symbolic_itinerary | marginal_shuffle | -0.365 | 0.411 | 0.116 | 0.634 |
| logistic_symbolic_itinerary | circular_shift | -2.473 | -0.541 | -0.449 | -0.052 |
| logistic_symbolic_itinerary | block_shuffle | 1.187 | 0.045 | -0.078 | 0.071 |
| logistic_return_intervals | marginal_shuffle | -0.469 | 0.000 | 2.060 | 0.093 |
| logistic_return_intervals | circular_shift | 0.963 | 0.000 | 1.478 | 0.475 |
| logistic_return_intervals | block_shuffle | 0.262 | 0.000 | 2.934 | 0.573 |

Z values, seed check:

| perimeter | surrogate | block_entropy_deficit_k4 | return_tail_alpha | recurrence_diag_mean | recurrence_determinism |
|---|---|---:|---:|---:|---:|
| logistic_orbit_values | marginal_shuffle | 740.441 | -0.210 | 1.617 | -0.194 |
| logistic_orbit_values | circular_shift | 72.240 | -2.985 | 1.846 | -0.917 |
| logistic_orbit_values | block_shuffle | 8.301 | -1.960 | 1.710 | -0.836 |
| logistic_symbolic_itinerary | marginal_shuffle | 1.435 | -1.848 | -0.642 | 0.264 |
| logistic_symbolic_itinerary | circular_shift | 0.590 | -0.457 | -0.620 | -0.167 |
| logistic_symbolic_itinerary | block_shuffle | 0.376 | -0.377 | -0.847 | -0.111 |
| logistic_return_intervals | marginal_shuffle | 0.682 | 0.000 | -0.221 | 0.751 |
| logistic_return_intervals | circular_shift | 0.857 | 0.000 | -0.165 | 1.303 |
| logistic_return_intervals | block_shuffle | 1.914 | 0.000 | -0.491 | 0.664 |

Raw denominator check for `logistic_orbit_values / block_entropy_deficit_k4`:

| run | surrogate | original | baseline mean | baseline std | z |
|---|---|---:|---:|---:|---:|
| main | marginal_shuffle | 0.339699144 | 0.005348953 | 0.000610199 | 547.936 |
| main | circular_shift | 0.339699144 | 0.338940830 | 0.000036383 | 20.842 |
| main | block_shuffle | 0.339699144 | 0.320447081 | 0.002680001 | 7.184 |
| seed | marginal_shuffle | 0.356679741 | 0.005451290 | 0.000474351 | 740.441 |
| seed | circular_shift | 0.356679741 | 0.355910140 | 0.000010653 | 72.240 |
| seed | block_shuffle | 0.356679741 | 0.335669705 | 0.002531149 | 8.301 |

## Risultato

1. **The orbit support survives the declared surrogate contract.**

   `logistic_orbit_values` keeps `block_entropy_deficit_k4` above gate against
   marginal shuffle, circular shift, and block shuffle in both runs. The
   surviving support is one observable, not a suite-wide endpoint support.

2. **The generating partition remains blank.**

   `logistic_symbolic_itinerary` has no replicated contract-stable observable.
   The main-run circular-shift value for `block_entropy_deficit_k4` is `z=-2.473`,
   but it does not survive marginal shuffle, block shuffle, or seed check.

3. **Return intervals stay outside the contract.**

   `logistic_return_intervals` shows `recurrence_diag_mean` against marginal and
   block shuffle in the main run, but not against circular shift and not in the
   seed check. The support is blank under the declared contract.

4. **The remaining logistic support lives at the cut-sensitive node.**

   Circular-shift denominators for orbit block entropy are very small
   (`0.000036383` main, `0.000010653` seed). The z gate passes, but the next
   falsification must separate local block grammar from artifact of the linear
   starting cut.

## Consecutio

`ORDER_DENOMINATOR_GATE` narrows again:

> The logistic orbit transfers through orbit block entropy under marginal,
> circular, and block surrogates. The generating itinerary and return intervals
> remain blank. The remaining logistic object is orbit-block-entropy support,
> not logistic dynamics as a whole.

Next experiment: falsify the residual support with a cyclic/start-invariant
block entropy observable and multiple block sizes. Declare support only if it
does not depend on the window's starting cut.

## Self-Audit: 5 Lenti

L1 hard constraint vs bias: blank claims are made only for replicated
contract-stable support. The report does not claim that all z values are zero.

L2 quantity vs ratio: raw original, baseline mean, baseline std, and z are
reported for the surviving observable because circular-shift denominators are
small.

L3 no silent patching: the claim under test changed from marginal shuffle to
surrogate contract because the seme directed that node. The old marginal result
is not discarded; it is reclassified as insufficient when it does not survive
the stronger contract.

L4 edge cases: main-run return-interval support is reported and then excluded
because it fails circular shift and seed check.

L5 re-discovery vs discovery: symbolic itinerary blank is consistent with the
classical Bernoulli coding of the logistic map at `r=4`. This report claims a
lab-gate scope, not a new theorem about the logistic map.

## Fonti

- Verificato: `tools/data/agent_field_live.md`
- Verificato: `tools/LAB_AGENT_CONTEXT.md`
- Verificato: `tools/data/seme.json`
- Verificato: `tools/exp_logistic_counter_scope_gate.py`
- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
- Verificato: `tools/data/logistic_surrogate_contract_gate_20260507_1042.json`
- Verificato: `tools/data/logistic_surrogate_contract_gate_20260507_1042_seedcheck.json`
- Verificato: `python -m py_compile tools/exp_logistic_surrogate_contract_gate.py`
- Inferito: the residual logistic support is cut-sensitive because circular
  shift preserves cyclic temporal order but changes the linear starting cut.

exec
/bin/bash -lc "python tools/dnd_domandatore.py --ask 'META tautology after logistic surrogate contract: separate local grammar from linear cut artifact in orbit block entropy'" in /opt/MM_D-ND
 succeeded in 153ms:

############################################################
# DOMANDATORE D-ND — Interrogazione
# 2026-05-07T14:20:29.991825
############################################################

  DOMANDA: META tautology after logistic surrogate contract: separate local grammar from linear cut artifact in orbit block entropy

  [DUALE   ] Se "META tautology after logistic surrogate contract: separate local grammar from linear cut artifact in orbit block entropy" e' vero, cosa dice il contrario (1/X)?
             Guida: Inverti la premessa. Se la conclusione tiene anche invertita, e' robusta.

  [CONFINE ] Tra il si e il no di "META tautology after logistic surrogate contract: separate local grammar from linear cut artifact in orbit block entropy", cosa c'e' al bordo?
             Guida: Il terzo incluso. La scelta non e' binaria — cosa esiste nel mezzo?

  [DOMINIO ] Se "META tautology after logistic surrogate contract: separate local grammar from linear cut artifact in orbit block entropy" vale qui, vale anche in un altro contesto?
             Guida: Cambia il dominio. Se l'effetto non si trasferisce, e' locale, non universale.

  [ROTTURA ] Cosa romperebbe "META tautology after logistic surrogate contract: separate local grammar from linear cut artifact in orbit block entropy"? Qual e' il controesempio?
             Guida: Cerca la falsificazione. Se non la trovi, il claim e' piu' forte.

  [SCALA   ] "META tautology after logistic surrogate contract: separate local grammar from linear cut artifact in orbit block entropy" vale a scala grande quanto a scala piccola?
             Guida: Testa ai limiti. Se funziona solo a una scala, non e' strutturale.

============================================================
  METODO: rispondi a ciascuna delle 5 domande.
  Le risposte sono il campo — la risultante emerge da sole.

  PRIORITY: [CONFINE] Tra il si e il no di "META tautology after logistic surrogate contract
============================================================

  Report: /opt/MM_D-ND/tools/data/domandatore/domandatore_20260507_1420.json

codex
La domanda che esce è il confine indicato dalla consecutio: il supporto orbit-block-entropy sopravvive perché misura grammatica locale oppure perché il taglio lineare del blocco crea un artefatto. Scrivo uno strumento riusabile che confronta block entropy lineare con una versione cyclic/start-invariant su più block size e repliche seed.
exec
/bin/bash -lc "sed -n '1,300p' tools/exp_logistic_counter_scope_gate.py" in /opt/MM_D-ND
 succeeded in 0ms:
#!/usr/bin/env python3
"""
exp_logistic_counter_scope_gate.py

Regressive test for ORDER_DENOMINATOR_GATE on the logistic counter-scope.

The 09:23 run showed that canonical gap observables do not read denominator
support in logistic return intervals. This tool keeps the same
original-vs-shuffle denominator gate and changes only the observable contract:

- symbolic block entropy deficit;
- return-tail exponent;
- recurrence-plot diagonal statistics.

These are logistic-native observables, not aliases of the canonical
SR/SR2/L1/L2/triple_var registry names.
"""

from __future__ import annotations

import argparse
import json
import math
from collections import Counter
from pathlib import Path

import numpy as np


OBSERVABLES_NATIVE_VERSION = "logistic-native-1.0.0-2026-05-07"
OBS_NAMES = [
    "block_entropy_deficit_k4",
    "return_tail_alpha",
    "recurrence_diag_mean",
    "recurrence_determinism",
]


def normalize(values: np.ndarray) -> np.ndarray:
    values = np.asarray(values, dtype=float)
    values = values[np.isfinite(values)]
    if len(values) == 0:
        return values
    values = values - float(np.min(values))
    scale = float(np.max(values))
    return values / scale if scale > 1e-15 else values


def logistic_orbit(n: int, rng: np.random.Generator, burn: int = 2000) -> np.ndarray:
    x = float(rng.random())
    out = np.empty(n, dtype=float)
    for i in range(n + burn):
        x = 4.0 * x * (1.0 - x)
        if i >= burn:
            out[i - burn] = x
    return out


def logistic_symbolic_itinerary(n: int, rng: np.random.Generator) -> np.ndarray:
    orbit = logistic_orbit(n, rng)
    return (orbit > 0.5).astype(float)


def logistic_return_intervals(n: int, rng: np.random.Generator) -> np.ndarray:
    threshold = 0.95
    burn = 2000
    returns: list[int] = []
    last_hit: int | None = None
    x = float(rng.random())
    i = 0
    max_steps = 50_000_000
    while len(returns) < n and i < max_steps:
        x = 4.0 * x * (1.0 - x)
        if i >= burn and x > threshold:
            if last_hit is not None:
                returns.append(i - last_hit)
            last_hit = i
        i += 1
    if len(returns) < n:
        raise RuntimeError(f"logistic generator produced {len(returns)} intervals, need {n}")
    return np.array(returns, dtype=float)


def quantile_symbols(values: np.ndarray, bins: int) -> np.ndarray:
    values = np.asarray(values, dtype=float)
    if len(np.unique(values)) <= bins:
        unique = {value: idx for idx, value in enumerate(sorted(set(values)))}
        return np.array([unique[value] for value in values], dtype=int)
    qs = np.quantile(values, np.linspace(0.0, 1.0, bins + 1)[1:-1])
    return np.searchsorted(qs, values, side="right").astype(int)


def block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
    symbols = quantile_symbols(values, bins)
    if len(symbols) < k + 1:
        return 0.0
    alphabet = max(2, int(np.max(symbols)) + 1)
    blocks = [tuple(symbols[i : i + k]) for i in range(len(symbols) - k + 1)]
    counts = np.array(list(Counter(blocks).values()), dtype=float)
    probs = counts / float(np.sum(counts))
    entropy = -float(np.sum(probs * np.log2(probs)))
    max_entropy = k * math.log2(alphabet)
    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0


def exceedance_intervals(values: np.ndarray, quantile: float = 0.95) -> np.ndarray:
    values = np.asarray(values, dtype=float)
    if len(values) < 3:
        return np.array([], dtype=float)
    threshold = float(np.quantile(values, quantile))
    hits = np.flatnonzero(values >= threshold)
    if len(hits) < 3:
        return np.array([], dtype=float)
    return np.diff(hits).astype(float)


def hill_tail_alpha(samples: np.ndarray) -> float:
    samples = np.asarray(samples, dtype=float)
    samples = samples[np.isfinite(samples) & (samples > 0)]
    if len(samples) < 16:
        return 0.0
    tail_count = max(8, int(0.20 * len(samples)))
    tail = np.sort(samples)[-tail_count:]
    xmin = float(tail[0])
    if xmin <= 0:
        return 0.0
    denom = float(np.mean(np.log(tail / xmin)))
    return float(1.0 / denom) if denom > 1e-15 else 0.0


def return_tail_alpha(values: np.ndarray) -> float:
    values = np.asarray(values, dtype=float)
    if np.all(values >= 1.0) and len(np.unique(values)) < max(64, len(values) // 2):
        intervals = values
    else:
        intervals = exceedance_intervals(values)
    return hill_tail_alpha(intervals)


def recurrence_diagonal_stats(values: np.ndarray, max_points: int = 1200, target_rr: float = 0.035) -> tuple[float, float]:
    values = normalize(values)
    if len(values) > max_points:
        idx = np.linspace(0, len(values) - 1, max_points).astype(int)
        values = values[idx]
    n = len(values)
    if n < 16:
        return 0.0, 0.0

    diff = np.abs(values[:, None] - values[None, :])
    upper = diff[np.triu_indices(n, k=1)]
    epsilon = float(np.quantile(upper, target_rr))
    rec = diff <= epsilon
    np.fill_diagonal(rec, False)

    lengths: list[int] = []
    recurrence_points = int(np.sum(rec))
    diagonal_points = 0
    for offset in range(-(n - 2), n - 1):
        diag = np.diagonal(rec, offset=offset)
        run = 0
        for item in diag:
            if item:
                run += 1
            else:
                if run >= 2:
                    lengths.append(run)
                    diagonal_points += run
                run = 0
        if run >= 2:
            lengths.append(run)
            diagonal_points += run

    if not lengths or recurrence_points == 0:
        return 0.0, 0.0
    return float(np.mean(lengths)), float(diagonal_points / recurrence_points)


def compute_native(values: np.ndarray, recurrence_max_points: int) -> dict[str, float]:
    diag_mean, determinism = recurrence_diagonal_stats(values, max_points=recurrence_max_points)
    return {
        "block_entropy_deficit_k4": block_entropy_deficit(values),
        "return_tail_alpha": return_tail_alpha(values),
        "recurrence_diag_mean": diag_mean,
        "recurrence_determinism": determinism,
    }


def beta_replace(base: np.ndarray, beta: float, rng: np.random.Generator) -> np.ndarray:
    illusory = rng.permutation(base)
    if beta <= 0.0:
        return base.copy()
    if beta >= 1.0:
        return illusory
    out = base.copy()
    mask = rng.random(len(base)) < beta
    out[mask] = illusory[mask]
    return out


def z_against_shuffle(
    values: np.ndarray,
    n_baseline: int,
    recurrence_max_points: int,
    rng: np.random.Generator,
) -> tuple[dict, dict, dict, dict]:
    original = compute_native(values, recurrence_max_points)
    baseline = {name: [] for name in OBS_NAMES}
    for _ in range(n_baseline):
        obs = compute_native(rng.permutation(values), recurrence_max_points)
        for name in OBS_NAMES:
            baseline[name].append(obs[name])

    means = {}
    sds = {}
    z = {}
    for name in OBS_NAMES:
        vals = np.array(baseline[name], dtype=float)
        means[name] = float(np.mean(vals))
        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
    return original, means, sds, z


def vector(row: dict, names: list[str]) -> np.ndarray:
    return np.array([row["observables"][name] for name in names], dtype=float)


def classify_layers(rows: list[dict], obs_names: list[str]) -> dict:
    if not obs_names:
        return {"observables": [], "endpoint_distance": 0.0, "layers": {}, "ambiguous_beta": []}

    by_beta: dict[float, list[dict]] = {}
    for row in rows:
        by_beta.setdefault(float(row["beta"]), []).append(row)

    coherent = np.array([vector(row, obs_names) for row in by_beta[0.0]], dtype=float)
    illusory = np.array([vector(row, obs_names) for row in by_beta[1.0]], dtype=float)
    endpoints = np.vstack([coherent, illusory])
    scale = np.std(endpoints, axis=0, ddof=1)
    scale[scale <= 1e-15] = 1.0
    coherent_centroid = np.mean(coherent, axis=0)
    illusory_centroid = np.mean(illusory, axis=0)
    endpoint_distance = float(np.linalg.norm((illusory_centroid - coherent_centroid) / scale))

    layers = {}
    ambiguous_beta = []
    for beta, beta_rows in sorted(by_beta.items()):
        coords = []
        margins = []
        labels = []
        for row in beta_rows:
            x = vector(row, obs_names)
            d_coherent = float(np.linalg.norm((x - coherent_centroid) / scale))
            d_illusory = float(np.linalg.norm((x - illusory_centroid) / scale))
            denom = d_coherent + d_illusory
            coord = float((d_coherent - d_illusory) / denom) if denom > 1e-15 else 0.0
            margin = float(abs(d_coherent - d_illusory) / denom) if denom > 1e-15 else 0.0
            coords.append(coord)
            margins.append(margin)
            labels.append("coherent" if d_coherent < d_illusory else "illusory")
        ambiguous_fraction = float(np.mean(np.array(margins) < 0.15))
        if ambiguous_fraction >= 0.5:
            ambiguous_beta.append(beta)
        layers[f"{beta:.3f}"] = {
            "coordinate_mean": float(np.mean(coords)),
            "margin_mean": float(np.mean(margins)),
            "ambiguous_fraction": ambiguous_fraction,
            "illusory_label_fraction": float(np.mean(np.array(labels) == "illusory")),
        }

    return {
        "observables": obs_names,
        "endpoint_distance": endpoint_distance,
        "layers": layers,
        "ambiguous_beta": ambiguous_beta,
    }


def summarize_gate(rows: list[dict], z_min: float) -> dict:
    by_beta: dict[float, list[dict]] = {}
    for row in rows:
        by_beta.setdefault(float(row["beta"]), []).append(row)

    layers = {}
    for beta, beta_rows in sorted(by_beta.items()):
        stable_counts = []
        stable_freq = {name: [] for name in OBS_NAMES}
        z_values = {name: [] for name in OBS_NAMES}
        for row in beta_rows:
            stable = [name for name in OBS_NAMES if abs(row["z"][name]) >= z_min]
            stable_counts.append(len(stable))
            for name in OBS_NAMES:
                stable_freq[name].append(1.0 if name in stable else 0.0)
                z_values[name].append(row["z"][name])
        layers[f"{beta:.3f}"] = {
            "stable_count_mean": float(np.mean(stable_counts)),
            "stable_frequency": {name: float(np.mean(vals)) for name, vals in stable_freq.items()},
            "z_mean": {name: float(np.mean(vals)) for name, vals in z_values.items()},
        }


exec
/bin/bash -lc "sed -n '1,220p' tools/observables_registry.py" in /opt/MM_D-ND
exec
/bin/bash -lc 'rg -n "block_entropy|entropy_deficit|logistic_orbit|OBSERVABLES_NATIVE_VERSION" tools/exp_*.py tools/*.py' in /opt/MM_D-ND
 succeeded in 0ms:
tools/exp_logistic_counter_scope_gate.py:30:OBSERVABLES_NATIVE_VERSION = "logistic-native-1.0.0-2026-05-07"
tools/exp_logistic_counter_scope_gate.py:32:    "block_entropy_deficit_k4",
tools/exp_logistic_counter_scope_gate.py:49:def logistic_orbit(n: int, rng: np.random.Generator, burn: int = 2000) -> np.ndarray:
tools/exp_logistic_counter_scope_gate.py:60:    orbit = logistic_orbit(n, rng)
tools/exp_logistic_counter_scope_gate.py:93:def block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
tools/exp_logistic_counter_scope_gate.py:181:        "block_entropy_deficit_k4": block_entropy_deficit(values),
tools/exp_logistic_counter_scope_gate.py:323:        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
tools/exp_logistic_counter_scope_gate.py:401:        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
tools/exp_logistic_counter_scope_gate.py:413:    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
tools/exp_logistic_surrogate_contract_gate.py:28:    OBSERVABLES_NATIVE_VERSION,
tools/exp_logistic_surrogate_contract_gate.py:31:    logistic_orbit,
tools/exp_logistic_surrogate_contract_gate.py:107:        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
tools/exp_logistic_surrogate_contract_gate.py:175:        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
tools/exp_logistic_surrogate_contract_gate.py:188:    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
tools/exp_logistic_counter_scope_gate.py:30:OBSERVABLES_NATIVE_VERSION = "logistic-native-1.0.0-2026-05-07"
tools/exp_logistic_counter_scope_gate.py:32:    "block_entropy_deficit_k4",
tools/exp_logistic_counter_scope_gate.py:49:def logistic_orbit(n: int, rng: np.random.Generator, burn: int = 2000) -> np.ndarray:
tools/exp_logistic_counter_scope_gate.py:60:    orbit = logistic_orbit(n, rng)
tools/exp_logistic_counter_scope_gate.py:93:def block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
tools/exp_logistic_counter_scope_gate.py:181:        "block_entropy_deficit_k4": block_entropy_deficit(values),
tools/exp_logistic_counter_scope_gate.py:323:        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
tools/exp_logistic_counter_scope_gate.py:401:        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
tools/exp_logistic_counter_scope_gate.py:413:    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
tools/exp_logistic_surrogate_contract_gate.py:28:    OBSERVABLES_NATIVE_VERSION,
tools/exp_logistic_surrogate_contract_gate.py:31:    logistic_orbit,
tools/exp_logistic_surrogate_contract_gate.py:107:        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
tools/exp_logistic_surrogate_contract_gate.py:175:        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
tools/exp_logistic_surrogate_contract_gate.py:188:    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")

 succeeded in 0ms:
"""observables_registry.py — Source of Truth per gli observables del lab D-ND.

Cristallizzato 2026-05-06 dalla **consecutio del cycle agent_20260506_0625**:

> "What opens now: the lab needs an observable registry. Labels like SR
>  cannot travel between reports unless they bind to a function definition.
>  Without that, META flags are not philosophical: the same label can
>  silently change the object under test."

## Il problema che ha creato il registry

Il cycle 06:25 ha auto-falsificato il finding del cycle 03:30 ("secondo asse
GUE") e nel farlo ha trovato **collision di nomi observable** tra script:

- `SR` in `exp_selective_layer_decoupling.py` = `spacing_ratio` (mean min/max
  ratio of consecutive gaps) — convention dominante (~6 script)
- `SR` in `exp_scale_selective_perturbation.py` = `spectral_rigidity(gaps)`
  (Δ₃(L) rigidity) — variante usata SOLO in 1 script

- `triple_var` in 3 script = `np.var(triple_sums)` (raw) — convention dominante
- `triple_var` in `exp_perturbation_dimensionality_audit.py` =
  `np.var(triples) / np.var(gaps)` (normalizzato) — variante in 1 script

Il lab autonomo che compara report tra script con osservabili "stesso nome,
funzione diversa" stava confrontando mele con arance.

## La soluzione (minimal, non invasiva)

Questo registry stabilisce il **nome canonico**: ciò che la maggioranza degli
script chiama già `SR`/`triple_var`/etc. Le varianti restano disponibili ma
con nomi ESPLICITI (`SR_local_rigidity`, `triple_var_normalized`) per evitare
mascheramento semantico.

## Come usarlo

```python
from observables_registry import OBSERVABLES_CANONICAL, OBSERVABLES_REGISTRY_VERSION

# Compute canonical observable suite for a sequence of gaps
results = {name: fn(gaps) for name, fn in OBSERVABLES_CANONICAL.items()}

# Or import individual canonical observable
from observables_registry import SR, triple_var, L1, L2, SR2

# For variants, import explicitly with disambiguating name
from observables_registry import SR_local_rigidity, triple_var_normalized
```

## Convention per i report

Ogni report agent (cycle) che usa observables DEVE includere nel suo header:

```
observables_registry: 1.0.0-2026-05-06
observables_used: [SR, SR2, L1, L2, triple_var]
```

Cycle che mescola canonical + variant DEVE indicare entrambi:

```
observables_used: [SR, SR_local_rigidity, ...]
```

Senza questo, i confronti cross-cycle sono inattendibili.

## Versioning

Cambiare una definizione canonica = bump del registry version e nota nel
changelog. Le definizioni canoniche sono **immutabili dentro una versione**.
"""
from __future__ import annotations

import numpy as np


OBSERVABLES_REGISTRY_VERSION = "1.0.0-2026-05-06"


# ─── Canonical observables (convention dominante nel codebase 2026-05-06) ───

def SR(gaps: np.ndarray) -> float:
    """**SR — Spacing Ratio** (canonical).

    Mean of `min(g_i, g_{i+1}) / max(g_i, g_{i+1})` over consecutive gaps.
    Range: (0, 1]. GUE → ~0.60. Poisson → ~0.39. Picket-fence → 1.

    NOTE: questa è la convention dominante in 6+ script del lab.
    Per la variante "local spectral rigidity Δ₃(L)" usare `SR_local_rigidity`.
    """
    if len(gaps) < 2:
        return 0.0
    s, s1 = gaps[:-1], gaps[1:]
    r = np.minimum(s, s1) / np.maximum(s, s1)
    r = r[np.isfinite(r) & (r > 0)]
    return float(np.mean(r)) if len(r) else 0.0


def SR2(gaps: np.ndarray) -> float:
    """**SR2 — Next-nearest Spacing Ratio** (canonical).

    Mean of `min(g_i, g_{i+2}) / max(g_i, g_{i+2})` skipping one gap.
    Probes lag-2 spacing structure.
    """
    if len(gaps) < 3:
        return 0.0
    s, s2 = gaps[:-2], gaps[2:]
    r = np.minimum(s, s2) / np.maximum(s, s2)
    r = r[np.isfinite(r) & (r > 0)]
    return float(np.mean(r)) if len(r) else 0.0


def L1(gaps: np.ndarray) -> float:
    """**L1 — Lag-1 Autocorrelation** (canonical).

    Standard ACF at lag 1 of the gap sequence.
    """
    if len(gaps) < 3:
        return 0.0
    g = gaps - np.mean(gaps)
    c0 = float(np.mean(g ** 2))
    if c0 <= 1e-15:
        return 0.0
    return float(np.mean(g[:-1] * g[1:]) / c0)


def L2(gaps: np.ndarray) -> float:
    """**L2 — Lag-2 Autocorrelation** (canonical)."""
    if len(gaps) < 4:
        return 0.0
    g = gaps - np.mean(gaps)
    c0 = float(np.mean(g ** 2))
    if c0 <= 1e-15:
        return 0.0
    return float(np.mean(g[:-2] * g[2:]) / c0)


def triple_var(gaps: np.ndarray) -> float:
    """**triple_var — Variance of consecutive gap triples** (canonical).

    Variance of `g_i + g_{i+1} + g_{i+2}` over the sequence (RAW, no
    normalization). Convention used in 3+ scripts. For the normalized
    version (variance ratio `var(triples) / var(gaps)`) use
    `triple_var_normalized`.
    """
    if len(gaps) < 3:
        return 0.0
    t = gaps[:-2] + gaps[1:-1] + gaps[2:]
    return float(np.var(t))


# Set canonico per uso "compute all" da report
OBSERVABLES_CANONICAL: dict[str, callable] = {
    "SR": SR,
    "SR2": SR2,
    "L1": L1,
    "L2": L2,
    "triple_var": triple_var,
}


# ─── Variants (esplicitamente nominate, no collision con canonical) ───

def SR_local_rigidity(gaps: np.ndarray, L: int = 10) -> float:
    """**SR_local_rigidity — Δ₃(L) Spectral Rigidity** (variant).

    Different observable than canonical `SR` (spacing ratio). Measures the
    average squared deviation of the cumulative spacing function from the
    best-fit straight line in a window of size L.

    Originated from `exp_scale_selective_perturbation.py` where it was
    locally named `SR` — registered here as `SR_local_rigidity` to avoid
    collision with canonical spacing-ratio definition.

    Use when explicitly studying spectral rigidity, NOT as alias for SR.
    """
    if len(gaps) < 5:
        return 0.0
    cumulative = np.cumsum(gaps)
    if cumulative[-1] <= 1e-15:
        return 0.0
    cumulative = cumulative / cumulative[-1] * len(cumulative)
    n = np.arange(1, len(cumulative) + 1, dtype=float)
    window = int(min(L * len(gaps) / cumulative[-1], len(gaps) // 2))
    if window < 5:
        return 0.0
    residuals = []
    for start in range(0, len(cumulative) - window, max(1, window // 2)):
        end = start + window
        x = n[start:end]
        y = cumulative[start:end]
        a, b = np.polyfit(x, y, 1)
        residuals.append(np.mean((y - (a * x + b)) ** 2))
    return float(np.mean(residuals)) if residuals else 0.0


def triple_var_normalized(gaps: np.ndarray) -> float:
    """**triple_var_normalized — Variance of triples / variance of gaps** (variant).

    Originated from `exp_perturbation_dimensionality_audit.py` where it was
    locally named `triple_var` — registered here as `triple_var_normalized`
    to avoid collision with canonical raw triple variance.

    Use when explicitly studying triple-variance scaling relative to
    single-gap variance, NOT as alias for triple_var.
    """
    if len(gaps) < 3:
        return 0.0
    triples = gaps[:-2] + gaps[1:-1] + gaps[2:]
    v = float(np.var(gaps))
    if v <= 1e-15:
        return 0.0
    return float(np.var(triples) / v)


# Set varianti, importabile esplicitamente
OBSERVABLES_VARIANTS: dict[str, callable] = {
    "SR_local_rigidity": SR_local_rigidity,
    "triple_var_normalized": triple_var_normalized,
}


codex
Farò due misure separate: `linear_block_entropy_deficit_k4`, sensibile ai bordi della sequenza, e `cyclic_block_entropy_deficit_k4`, che conta anche i blocchi che attraversano il wrap ed è invariante alle rotazioni. Se il supporto resta solo nella misura lineare o cambia con block size, il claim va ristretto al nodo del taglio; se resta nella misura ciclica contro shuffle e block-shuffle, è grammatica locale dell’orbita.
apply patch
patch: completed
/opt/MM_D-ND/tools/exp_logistic_cyclic_block_entropy_gate.py
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

codex
Lo strumento è aggiunto. Ora lo compilo e lo eseguo con il seed del report, poi faccio un seed check separato: il finding deve reggere almeno una replica prima di entrare nel report.
exec
/bin/bash -lc 'python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py' in /opt/MM_D-ND
 succeeded in 0ms:
exec
/bin/bash -lc 'python tools/exp_logistic_cyclic_block_entropy_gate.py --out tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json --seed 202605071419' in /opt/MM_D-ND
exec
/bin/bash -lc 'python tools/exp_logistic_cyclic_block_entropy_gate.py --out tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json --seed 202605071420 --n-baseline 28 --n-rotations 28' in /opt/MM_D-ND
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

 succeeded in 7351ms:
observables_native_version=logistic-native-1.0.0-2026-05-07
observables_cyclic_version=logistic-cyclic-block-entropy-1.0.0-2026-05-07
observables_used=['linear_block_entropy_deficit_k4', 'cyclic_block_entropy_deficit_k4']
perimeter n marginal_stable rotation_invariant cyclic_block_sizes
        logistic_orbit_values  4096 linear_block_entropy_deficit_k4,cyclic_block_entropy_deficit_k4                                cyclic_block_entropy_deficit_k4 [4, 8, 16, 32, 64, 128, 256]
  logistic_symbolic_itinerary  4096                                                             []                                cyclic_block_entropy_deficit_k4 []
    logistic_return_intervals  4096                                                             []                                cyclic_block_entropy_deficit_k4 []
saved tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json

diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

 succeeded in 8247ms:
observables_native_version=logistic-native-1.0.0-2026-05-07
observables_cyclic_version=logistic-cyclic-block-entropy-1.0.0-2026-05-07
observables_used=['linear_block_entropy_deficit_k4', 'cyclic_block_entropy_deficit_k4']
perimeter n marginal_stable rotation_invariant cyclic_block_sizes
        logistic_orbit_values  4096 linear_block_entropy_deficit_k4,cyclic_block_entropy_deficit_k4                                cyclic_block_entropy_deficit_k4 [4, 8, 16, 32, 64, 128, 256]
  logistic_symbolic_itinerary  4096 linear_block_entropy_deficit_k4,cyclic_block_entropy_deficit_k4                                cyclic_block_entropy_deficit_k4 [4, 256]
    logistic_return_intervals  4096                                                             []                                cyclic_block_entropy_deficit_k4 [16]
saved tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json

codex
Il deposito principale distingue bene il nodo: l’orbita conserva il supporto ciclico su tutti i block size; itinerary e return intervals non replicano nel seed check. Ora estraggo i numeri grezzi per il report e controllo che non ci siano claim più forti dei dati.
exec
/bin/bash -lc "jq '.matrix' tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json" in /opt/MM_D-ND
 succeeded in 0ms:
{
  "logistic_orbit_values": {
    "n": 4096,
    "marginal_stable": [
      "linear_block_entropy_deficit_k4",
      "cyclic_block_entropy_deficit_k4"
    ],
    "rotation_invariant": [
      "cyclic_block_entropy_deficit_k4"
    ],
    "linear_block_shuffle_support_sizes": [
      4,
      8,
      16,
      32,
      64,
      128,
      256
    ],
    "cyclic_block_shuffle_support_sizes": [
      4,
      8,
      16,
      32,
      64,
      128,
      256
    ],
    "cyclic_support_all_declared_block_sizes": true,
    "marginal_z": {
      "linear_block_entropy_deficit_k4": 725.4132349614537,
      "cyclic_block_entropy_deficit_k4": 732.166989286076
    },
    "rotation_max_abs_delta": {
      "linear_block_entropy_deficit_k4": 0.00022003480307175582,
      "cyclic_block_entropy_deficit_k4": 2.220446049250313E-16
    },
    "block_shuffle_z": {
      "4": {
        "linear_block_entropy_deficit_k4": 53.62917093435737,
        "cyclic_block_entropy_deficit_k4": 53.91338223361933
      },
      "8": {
        "linear_block_entropy_deficit_k4": 41.84053686844851,
        "cyclic_block_entropy_deficit_k4": 41.01716599999066
      },
      "16": {
        "linear_block_entropy_deficit_k4": 22.170912113370143,
        "cyclic_block_entropy_deficit_k4": 22.309451056537938
      },
      "32": {
        "linear_block_entropy_deficit_k4": 10.780893284473974,
        "cyclic_block_entropy_deficit_k4": 10.883861597513436
      },
      "64": {
        "linear_block_entropy_deficit_k4": 8.183152981664213,
        "cyclic_block_entropy_deficit_k4": 8.076792497944915
      },
      "128": {
        "linear_block_entropy_deficit_k4": 7.349868878267287,
        "cyclic_block_entropy_deficit_k4": 6.709438624660817
      },
      "256": {
        "linear_block_entropy_deficit_k4": 3.9492137539105263,
        "cyclic_block_entropy_deficit_k4": 3.798199555191819
      }
    }
  },
  "logistic_symbolic_itinerary": {
    "n": 4096,
    "marginal_stable": [
      "linear_block_entropy_deficit_k4",
      "cyclic_block_entropy_deficit_k4"
    ],
    "rotation_invariant": [
      "cyclic_block_entropy_deficit_k4"
    ],
    "linear_block_shuffle_support_sizes": [
      4,
      256
    ],
    "cyclic_block_shuffle_support_sizes": [
      4,
      256
    ],
    "cyclic_support_all_declared_block_sizes": false,
    "marginal_z": {
      "linear_block_entropy_deficit_k4": 5.419912485943431,
      "cyclic_block_entropy_deficit_k4": 5.2343946207827265
    },
    "rotation_max_abs_delta": {
      "linear_block_entropy_deficit_k4": 0.000048738862294617213,
      "cyclic_block_entropy_deficit_k4": 1.1102230246251565E-16
    },
    "block_shuffle_z": {
      "4": {
        "linear_block_entropy_deficit_k4": 3.9432239452750886,
        "cyclic_block_entropy_deficit_k4": 3.8511125772684216
      },
      "8": {
        "linear_block_entropy_deficit_k4": 1.0666393642993683,
        "cyclic_block_entropy_deficit_k4": 1.020742780992769
      },
      "16": {
        "linear_block_entropy_deficit_k4": 0.6659834940061882,
        "cyclic_block_entropy_deficit_k4": 0.5992314173478364
      },
      "32": {
        "linear_block_entropy_deficit_k4": 0.33097563562272214,
        "cyclic_block_entropy_deficit_k4": 0.2613707184925538
      },
      "64": {
        "linear_block_entropy_deficit_k4": 0.43783864758460267,
        "cyclic_block_entropy_deficit_k4": 0.2746458044414302
      },
      "128": {
        "linear_block_entropy_deficit_k4": 1.4376948362403754,
        "cyclic_block_entropy_deficit_k4": 1.0254039303091909
      },
      "256": {
        "linear_block_entropy_deficit_k4": 2.7647982988436395,
        "cyclic_block_entropy_deficit_k4": 2.498569598022473
      }
    }
  },
  "logistic_return_intervals": {
    "n": 4096,
    "marginal_stable": [],
    "rotation_invariant": [
      "cyclic_block_entropy_deficit_k4"
    ],
    "linear_block_shuffle_support_sizes": [
      16
    ],
    "cyclic_block_shuffle_support_sizes": [
      16
    ],
    "cyclic_support_all_declared_block_sizes": false,
    "marginal_z": {
      "linear_block_entropy_deficit_k4": 1.4810314162497573,
      "cyclic_block_entropy_deficit_k4": 1.3368439875384537
    },
    "rotation_max_abs_delta": {
      "linear_block_entropy_deficit_k4": 0.000231396481246815,
      "cyclic_block_entropy_deficit_k4": 2.220446049250313E-16
    },
    "block_shuffle_z": {
      "4": {
        "linear_block_entropy_deficit_k4": 1.7455853117758662,
        "cyclic_block_entropy_deficit_k4": 1.5911459179256517
      },
      "8": {
        "linear_block_entropy_deficit_k4": 1.4235052843587659,
        "cyclic_block_entropy_deficit_k4": 1.3508744825108339
      },
      "16": {
        "linear_block_entropy_deficit_k4": 3.4169730618920067,
        "cyclic_block_entropy_deficit_k4": 2.927729265249378
      },
      "32": {
        "linear_block_entropy_deficit_k4": 1.5995463175972715,
        "cyclic_block_entropy_deficit_k4": 1.3294117035471964
      },
      "64": {
        "linear_block_entropy_deficit_k4": 0.7731686034833056,
        "cyclic_block_entropy_deficit_k4": 0.2073017457782347
      },
      "128": {
        "linear_block_entropy_deficit_k4": 0.4458689492738616,
        "cyclic_block_entropy_deficit_k4": 0.00013593183992072346
      },
      "256": {
        "linear_block_entropy_deficit_k4": -0.8149709041129192,
        "cyclic_block_entropy_deficit_k4": -0.929574293139767
      }
    }
  }
}

exec
/bin/bash -lc "jq '.matrix' tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json" in /opt/MM_D-ND
 succeeded in 0ms:
{
  "logistic_orbit_values": {
    "n": 4096,
    "marginal_stable": [
      "linear_block_entropy_deficit_k4",
      "cyclic_block_entropy_deficit_k4"
    ],
    "rotation_invariant": [
      "cyclic_block_entropy_deficit_k4"
    ],
    "linear_block_shuffle_support_sizes": [
      4,
      8,
      16,
      32,
      64,
      128,
      256
    ],
    "cyclic_block_shuffle_support_sizes": [
      4,
      8,
      16,
      32,
      64,
      128,
      256
    ],
    "cyclic_support_all_declared_block_sizes": true,
    "marginal_z": {
      "linear_block_entropy_deficit_k4": 474.8772484475516,
      "cyclic_block_entropy_deficit_k4": 469.6636578661176
    },
    "rotation_max_abs_delta": {
      "linear_block_entropy_deficit_k4": 0.0008022082060150648,
      "cyclic_block_entropy_deficit_k4": 1.1102230246251565E-16
    },
    "block_shuffle_z": {
      "4": {
        "linear_block_entropy_deficit_k4": 55.185843763881145,
        "cyclic_block_entropy_deficit_k4": 55.518101103290974
      },
      "8": {
        "linear_block_entropy_deficit_k4": 30.807960457241197,
        "cyclic_block_entropy_deficit_k4": 31.16170969413929
      },
      "16": {
        "linear_block_entropy_deficit_k4": 24.17642472229746,
        "cyclic_block_entropy_deficit_k4": 24.414185659598974
      },
      "32": {
        "linear_block_entropy_deficit_k4": 12.213955870947284,
        "cyclic_block_entropy_deficit_k4": 11.993129982325307
      },
      "64": {
        "linear_block_entropy_deficit_k4": 8.861195937803462,
        "cyclic_block_entropy_deficit_k4": 8.386440084297782
      },
      "128": {
        "linear_block_entropy_deficit_k4": 5.592145614242373,
        "cyclic_block_entropy_deficit_k4": 5.129602932644606
      },
      "256": {
        "linear_block_entropy_deficit_k4": 4.831613060926332,
        "cyclic_block_entropy_deficit_k4": 4.085613917428004
      }
    }
  },
  "logistic_symbolic_itinerary": {
    "n": 4096,
    "marginal_stable": [],
    "rotation_invariant": [
      "cyclic_block_entropy_deficit_k4"
    ],
    "linear_block_shuffle_support_sizes": [],
    "cyclic_block_shuffle_support_sizes": [],
    "cyclic_support_all_declared_block_sizes": false,
    "marginal_z": {
      "linear_block_entropy_deficit_k4": -0.02254313263691325,
      "cyclic_block_entropy_deficit_k4": -0.020249813216330514
    },
    "rotation_max_abs_delta": {
      "linear_block_entropy_deficit_k4": 0.00002722107567576515,
      "cyclic_block_entropy_deficit_k4": 2.220446049250313E-16
    },
    "block_shuffle_z": {
      "4": {
        "linear_block_entropy_deficit_k4": -0.37125655206665054,
        "cyclic_block_entropy_deficit_k4": -0.3751752948412922
      },
      "8": {
        "linear_block_entropy_deficit_k4": 0.39463774657323947,
        "cyclic_block_entropy_deficit_k4": 0.377842952611322
      },
      "16": {
        "linear_block_entropy_deficit_k4": 1.2089598425792916,
        "cyclic_block_entropy_deficit_k4": 1.265537746039308
      },
      "32": {
        "linear_block_entropy_deficit_k4": 1.4782519199579252,
        "cyclic_block_entropy_deficit_k4": 1.462719957376989
      },
      "64": {
        "linear_block_entropy_deficit_k4": 1.0179680317033593,
        "cyclic_block_entropy_deficit_k4": 1.0591838302179577
      },
      "128": {
        "linear_block_entropy_deficit_k4": 0.3452919589843032,
        "cyclic_block_entropy_deficit_k4": 0.3150458023114203
      },
      "256": {
        "linear_block_entropy_deficit_k4": -0.43736260540661315,
        "cyclic_block_entropy_deficit_k4": -0.3333312501070648
      }
    }
  },
  "logistic_return_intervals": {
    "n": 4096,
    "marginal_stable": [],
    "rotation_invariant": [
      "cyclic_block_entropy_deficit_k4"
    ],
    "linear_block_shuffle_support_sizes": [],
    "cyclic_block_shuffle_support_sizes": [],
    "cyclic_support_all_declared_block_sizes": false,
    "marginal_z": {
      "linear_block_entropy_deficit_k4": 1.310245188222813,
      "cyclic_block_entropy_deficit_k4": 1.3515226296624683
    },
    "rotation_max_abs_delta": {
      "linear_block_entropy_deficit_k4": 0.0001454224433942164,
      "cyclic_block_entropy_deficit_k4": 2.220446049250313E-16
    },
    "block_shuffle_z": {
      "4": {
        "linear_block_entropy_deficit_k4": 1.728372239543694,
        "cyclic_block_entropy_deficit_k4": 1.8888442949278046
      },
      "8": {
        "linear_block_entropy_deficit_k4": 0.7786304463875807,
        "cyclic_block_entropy_deficit_k4": 0.7839607305803371
      },
      "16": {
        "linear_block_entropy_deficit_k4": 0.33387397672040053,
        "cyclic_block_entropy_deficit_k4": 0.37370550428820537
      },
      "32": {
        "linear_block_entropy_deficit_k4": -0.5945633965961548,
        "cyclic_block_entropy_deficit_k4": -0.5559796474316623
      },
      "64": {
        "linear_block_entropy_deficit_k4": 0.04111206689387797,
        "cyclic_block_entropy_deficit_k4": 0.1577417050380755
      },
      "128": {
        "linear_block_entropy_deficit_k4": -0.9519778241065401,
        "cyclic_block_entropy_deficit_k4": -1.1606718070422684
      },
      "256": {
        "linear_block_entropy_deficit_k4": -0.08926011111520175,
        "cyclic_block_entropy_deficit_k4": -0.24460534452913463
      }
    }
  }
}

exec
/bin/bash -lc "jq '.perimeters.logistic_orbit_values.marginal_shuffle, .perimeters.logistic_orbit_values.rotation_sensitivity, .perimeters.logistic_orbit_values.block_shuffle_scan' tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json" in /opt/MM_D-ND
 succeeded in 0ms:
{
  "original": {
    "linear_block_entropy_deficit_k4": 0.35215475712872935,
    "cyclic_block_entropy_deficit_k4": 0.35139858604979424
  },
  "baseline_mean": {
    "linear_block_entropy_deficit_k4": 0.005365667876678006,
    "cyclic_block_entropy_deficit_k4": 0.005372939711145893
  },
  "baseline_std": {
    "linear_block_entropy_deficit_k4": 0.0007302710129528407,
    "cyclic_block_entropy_deficit_k4": 0.0007367520150713608
  },
  "z": {
    "linear_block_entropy_deficit_k4": 474.8772484475516,
    "cyclic_block_entropy_deficit_k4": 469.6636578661176
  }
}
{
  "linear_block_entropy_deficit_k4": {
    "original": 0.35215475712872935,
    "rotation_mean": 0.35140433973074503,
    "rotation_std": 0.00006718179232746986,
    "max_abs_delta": 0.0008022082060150648
  },
  "cyclic_block_entropy_deficit_k4": {
    "original": 0.35139858604979424,
    "rotation_mean": 0.3513985860497944,
    "rotation_std": 1.4647964430338923E-16,
    "max_abs_delta": 1.1102230246251565E-16
  }
}
{
  "4": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.35215475712872935,
      "cyclic_block_entropy_deficit_k4": 0.35139858604979424
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.1739183569488027,
      "cyclic_block_entropy_deficit_k4": 0.1739280992152901
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0032297485736112177,
      "cyclic_block_entropy_deficit_k4": 0.0031966238633472305
    },
    "z": {
      "linear_block_entropy_deficit_k4": 55.185843763881145,
      "cyclic_block_entropy_deficit_k4": 55.518101103290974
    }
  },
  "8": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.35215475712872935,
      "cyclic_block_entropy_deficit_k4": 0.35139858604979424
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.23866736768066335,
      "cyclic_block_entropy_deficit_k4": 0.23855543400462412
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0036837034248202423,
      "cyclic_block_entropy_deficit_k4": 0.0036212118382706387
    },
    "z": {
      "linear_block_entropy_deficit_k4": 30.807960457241197,
      "cyclic_block_entropy_deficit_k4": 31.16170969413929
    }
  },
  "16": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.35215475712872935,
      "cyclic_block_entropy_deficit_k4": 0.35139858604979424
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.28654510036975755,
      "cyclic_block_entropy_deficit_k4": 0.28634327941778986
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0027137865715297946,
      "cyclic_block_entropy_deficit_k4": 0.0026646519174980733
    },
    "z": {
      "linear_block_entropy_deficit_k4": 24.17642472229746,
      "cyclic_block_entropy_deficit_k4": 24.414185659598974
    }
  },
  "32": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.35215475712872935,
      "cyclic_block_entropy_deficit_k4": 0.35139858604979424
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.3156362858228063,
      "cyclic_block_entropy_deficit_k4": 0.3153510162158965
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0029898971055551046,
      "cyclic_block_entropy_deficit_k4": 0.0030056849118638993
    },
    "z": {
      "linear_block_entropy_deficit_k4": 12.213955870947284,
      "cyclic_block_entropy_deficit_k4": 11.993129982325307
    }
  },
  "64": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.35215475712872935,
      "cyclic_block_entropy_deficit_k4": 0.35139858604979424
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.33335409958072976,
      "cyclic_block_entropy_deficit_k4": 0.3331508767639117
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.002121683989380326,
      "cyclic_block_entropy_deficit_k4": 0.0021758587794657174
    },
    "z": {
      "linear_block_entropy_deficit_k4": 8.861195937803462,
      "cyclic_block_entropy_deficit_k4": 8.386440084297782
    }
  },
  "128": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.35215475712872935,
      "cyclic_block_entropy_deficit_k4": 0.35139858604979424
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.3432910562137657,
      "cyclic_block_entropy_deficit_k4": 0.34300183064878303
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0015850268441488882,
      "cyclic_block_entropy_deficit_k4": 0.0016369211245522645
    },
    "z": {
      "linear_block_entropy_deficit_k4": 5.592145614242373,
      "cyclic_block_entropy_deficit_k4": 5.129602932644606
    }
  },
  "256": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.35215475712872935,
      "cyclic_block_entropy_deficit_k4": 0.35139858604979424
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.34677199469092124,
      "cyclic_block_entropy_deficit_k4": 0.3465100277542525
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0011140715057128578,
      "cyclic_block_entropy_deficit_k4": 0.0011965296756721482
    },
    "z": {
      "linear_block_entropy_deficit_k4": 4.831613060926332,
      "cyclic_block_entropy_deficit_k4": 4.085613917428004
    }
  }
}

exec
/bin/bash -lc "jq '.perimeters.logistic_orbit_values.marginal_shuffle, .perimeters.logistic_orbit_values.rotation_sensitivity, .perimeters.logistic_orbit_values.block_shuffle_scan' tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json" in /opt/MM_D-ND
 succeeded in 0ms:
{
  "original": {
    "linear_block_entropy_deficit_k4": 0.3427794829406815,
    "cyclic_block_entropy_deficit_k4": 0.3425929482087746
  },
  "baseline_mean": {
    "linear_block_entropy_deficit_k4": 0.005435028115678701,
    "cyclic_block_entropy_deficit_k4": 0.005430166400698332
  },
  "baseline_std": {
    "linear_block_entropy_deficit_k4": 0.00046503763450487406,
    "cyclic_block_entropy_deficit_k4": 0.00046049984053069394
  },
  "z": {
    "linear_block_entropy_deficit_k4": 725.4132349614537,
    "cyclic_block_entropy_deficit_k4": 732.166989286076
  }
}
{
  "linear_block_entropy_deficit_k4": {
    "original": 0.3427794829406815,
    "rotation_mean": 0.3425812236565303,
    "rotation_std": 0.000020374710285476375,
    "max_abs_delta": 0.00022003480307175582
  },
  "cyclic_block_entropy_deficit_k4": {
    "original": 0.3425929482087746,
    "rotation_mean": 0.34259294820877456,
    "rotation_std": 8.917525927715206E-17,
    "max_abs_delta": 2.220446049250313E-16
  }
}
{
  "4": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.3427794829406815,
      "cyclic_block_entropy_deficit_k4": 0.3425929482087746
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.16572884506293176,
      "cyclic_block_entropy_deficit_k4": 0.16569418482815318
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0033013868160382616,
      "cyclic_block_entropy_deficit_k4": 0.0032811661233583455
    },
    "z": {
      "linear_block_entropy_deficit_k4": 53.62917093435737,
      "cyclic_block_entropy_deficit_k4": 53.91338223361933
    }
  },
  "8": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.3427794829406815,
      "cyclic_block_entropy_deficit_k4": 0.3425929482087746
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.2307815463516985,
      "cyclic_block_entropy_deficit_k4": 0.23061947684614487
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.002676780581021641,
      "cyclic_block_entropy_deficit_k4": 0.0027299173073696815
    },
    "z": {
      "linear_block_entropy_deficit_k4": 41.84053686844851,
      "cyclic_block_entropy_deficit_k4": 41.01716599999066
    }
  },
  "16": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.3427794829406815,
      "cyclic_block_entropy_deficit_k4": 0.3425929482087746
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.27841009108643877,
      "cyclic_block_entropy_deficit_k4": 0.27822195839170694
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0029033262828832757,
      "cyclic_block_entropy_deficit_k4": 0.0028853686114434143
    },
    "z": {
      "linear_block_entropy_deficit_k4": 22.170912113370143,
      "cyclic_block_entropy_deficit_k4": 22.309451056537938
    }
  },
  "32": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.3427794829406815,
      "cyclic_block_entropy_deficit_k4": 0.3425929482087746
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.30699453486252765,
      "cyclic_block_entropy_deficit_k4": 0.30675422931321394
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.003319293414182041,
      "cyclic_block_entropy_deficit_k4": 0.003292831186290398
    },
    "z": {
      "linear_block_entropy_deficit_k4": 10.780893284473974,
      "cyclic_block_entropy_deficit_k4": 10.883861597513436
    }
  },
  "64": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.3427794829406815,
      "cyclic_block_entropy_deficit_k4": 0.3425929482087746
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.3235930524276893,
      "cyclic_block_entropy_deficit_k4": 0.32331274404905364
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.00234462566641278,
      "cyclic_block_entropy_deficit_k4": 0.0023871114882085555
    },
    "z": {
      "linear_block_entropy_deficit_k4": 8.183152981664213,
      "cyclic_block_entropy_deficit_k4": 8.076792497944915
    }
  },
  "128": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.3427794829406815,
      "cyclic_block_entropy_deficit_k4": 0.3425929482087746
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.3329420841062045,
      "cyclic_block_entropy_deficit_k4": 0.3326354457379981
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0013384454875875454,
      "cyclic_block_entropy_deficit_k4": 0.001484103667656684
    },
    "z": {
      "linear_block_entropy_deficit_k4": 7.349868878267287,
      "cyclic_block_entropy_deficit_k4": 6.709438624660817
    }
  },
  "256": {
    "original": {
      "linear_block_entropy_deficit_k4": 0.3427794829406815,
      "cyclic_block_entropy_deficit_k4": 0.3425929482087746
    },
    "baseline_mean": {
      "linear_block_entropy_deficit_k4": 0.33795104638377127,
      "cyclic_block_entropy_deficit_k4": 0.3375760125837197
    },
    "baseline_std": {
      "linear_block_entropy_deficit_k4": 0.0012226323662853386,
      "cyclic_block_entropy_deficit_k4": 0.0013208720479673502
    },
    "z": {
      "linear_block_entropy_deficit_k4": 3.9492137539105263,
      "cyclic_block_entropy_deficit_k4": 3.798199555191819
    }
  }
}

diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

codex
Il risultato utile è stretto: l’orbita non cade quando rimuovo il taglio lineare, perché la misura ciclica replica contro marginal shuffle e contro block-shuffle da 4 a 256 in entrambi i seed. Le due sequenze counter-scope restano blank nel criterio replicato, quindi non estendo il claim a tutta la logistica.
apply patch
patch: completed
/opt/MM_D-ND/tools/data/reports/agent_20260507_1419.md
diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

exec
/bin/bash -lc 'rg -n "Circular-shift z uses|surrogate-contract" tools/data/seme.json' in /opt/MM_D-ND
 succeeded in 0ms:
41:      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",

diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

codex
Il report è scritto. Aggiorno ora il seme solo sul claim già vivo di `ORDER_DENOMINATOR_GATE`, perché il finding modifica quel perimetro e non introduce una tensione separata.
apply patch
patch: completed
/opt/MM_D-ND/tools/data/seme.json
diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/data/seme.json b/tools/data/seme.json
index db10e4eb50cd5826f4a65ec6b037b68b83a3d42a..750ca0c31a52b3bdb98be23fd0c4ea3092bdee1e
--- a/tools/data/seme.json
+++ b/tools/data/seme.json
@@ -38,7 +38,7 @@
     {
       "tipo": "vincolo",
       "id": "ORDER_DENOMINATOR_GATE",
-      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
+      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Nel perimetro cyclic-cut agent_20260507_1419, il residuo logistic_orbit_values non e artefatto del taglio lineare: cyclic_block_entropy_deficit_k4 e invariato sotto rotazione e replica contro marginal_shuffle e block_shuffle size 4-256 in run e seed check. Logistic_symbolic_itinerary e logistic_return_intervals restano blank nel criterio replicato. Il supporto logistic rimasto e orbit-local block grammar, non return/generating-partition support.",
       "intensita": 0.92,
       "porta": "META",
       "manuale": true,
@@ -309,4 +309,4 @@
       "timestamp": "2026-04-04T19:34:08.014Z"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/data/seme.json b/tools/data/seme.json
index db10e4eb50cd5826f4a65ec6b037b68b83a3d42a..750ca0c31a52b3bdb98be23fd0c4ea3092bdee1e
--- a/tools/data/seme.json
+++ b/tools/data/seme.json
@@ -38,7 +38,7 @@
     {
       "tipo": "vincolo",
       "id": "ORDER_DENOMINATOR_GATE",
-      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
+      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Nel perimetro cyclic-cut agent_20260507_1419, il residuo logistic_orbit_values non e artefatto del taglio lineare: cyclic_block_entropy_deficit_k4 e invariato sotto rotazione e replica contro marginal_shuffle e block_shuffle size 4-256 in run e seed check. Logistic_symbolic_itinerary e logistic_return_intervals restano blank nel criterio replicato. Il supporto logistic rimasto e orbit-local block grammar, non return/generating-partition support.",
       "intensita": 0.92,
       "porta": "META",
       "manuale": true,
@@ -309,4 +309,4 @@
       "timestamp": "2026-04-04T19:34:08.014Z"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

exec
/bin/bash -lc "sed -n '1,260p' tools/data/reports/agent_20260507_1419.md" in /opt/MM_D-ND
 succeeded in 0ms:
# Agent Report — Logistic Cyclic Block Entropy Gate

timestamp: 2026-05-07 14:19 UTC
category: meta_cut_artifact_falsification
verdict: orbit_local_grammar_not_linear_cut
observables_registry: not used for canonical observables
observables_native_version: logistic-native-1.0.0-2026-05-07
observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
tool: tools/exp_logistic_cyclic_block_entropy_gate.py
data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json

## Claim Under Test

Verificato dal seme: il residuo del run 10:42 era
`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
locale dell'orbita da artefatto del taglio lineare.

Questo run confronta:

- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
  una rotazione circolare non cambia l'osservabile.
- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.

Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
`marginal_shuffle` e contro tutti i block size dichiarati, con
`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.

## Deposito Numerico

Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
`n_rotations=32`, `seed=202605071419`.

Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.

| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
|---|---|---|---|
| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
| logistic_return_intervals | [] | cyclic | 16 |

Seed check:

| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
|---|---|---|---|
| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
| logistic_symbolic_itinerary | [] | cyclic | [] |
| logistic_return_intervals | [] | cyclic | [] |

Orbit values, run principale:

| test | linear z / delta | cyclic z / delta |
|---|---:|---:|
| marginal_shuffle z | 725.413 | 732.167 |
| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
| block_shuffle size 4 z | 53.629 | 53.913 |
| block_shuffle size 8 z | 41.841 | 41.017 |
| block_shuffle size 16 z | 22.171 | 22.309 |
| block_shuffle size 32 z | 10.781 | 10.884 |
| block_shuffle size 64 z | 8.183 | 8.077 |
| block_shuffle size 128 z | 7.350 | 6.709 |
| block_shuffle size 256 z | 3.949 | 3.798 |

Orbit values, seed check:

| test | linear z / delta | cyclic z / delta |
|---|---:|---:|
| marginal_shuffle z | 474.877 | 469.664 |
| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
| block_shuffle size 4 z | 55.186 | 55.518 |
| block_shuffle size 8 z | 30.808 | 31.162 |
| block_shuffle size 16 z | 24.176 | 24.414 |
| block_shuffle size 32 z | 12.214 | 11.993 |
| block_shuffle size 64 z | 8.861 | 8.386 |
| block_shuffle size 128 z | 5.592 | 5.130 |
| block_shuffle size 256 z | 4.832 | 4.086 |

## Risultato

1. **The orbit support is local grammar, not the linear starting cut.**

   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
   and every declared block-shuffle size in both runs.

2. **The support weakens with larger preserved blocks but stays inside gate.**

   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
   in the main run and from `55.518` to `4.086` in seed check as block size
   moves from `4` to `256`. The block-size scan does not make the support blank
   in this perimetro.

3. **The generating partition and return intervals remain counter-scope.**

   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
   non-replicated hits in the main run, then blank support in seed check. Their
   cyclic rotation invariance is a property of the observable construction, not
   evidence of logistic support.

## Consecutio

`ORDER_DENOMINATOR_GATE` narrows without collapsing:

> The logistic counter-scope keeps one replicated object: orbit-local block
> grammar. The object is cyclic/start-invariant and survives block-shuffle
> scans from 4 to 256 samples. The generating itinerary and return intervals
> remain blank.

Next experiment: move from entropy deficit to explicit block-transition graph.
The discriminant is whether the surviving grammar lives in short word counts
alone or in directed transitions between words.

## Self-Audit: 5 Lenti

L1 hard constraint vs bias: "blank" is used only for replicated support under
the declared gate. Main-run hits for itinerary and return intervals are reported
and excluded because seed check removes them.

L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
uses `abs(z)>=2`, not percentages across unequal denominators.

L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
observable is tested at its stated weak node: circular cut sensitivity.

L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
at block size `16`, are kept in the table and not generalized.

L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
report claims a lab-gate scope for a native observable, not a new theorem about
the logistic map.

## Fonti

- Verificato: `tools/data/agent_field_live.md`
- Verificato: `tools/LAB_AGENT_CONTEXT.md`
- Verificato: `tools/data/seme.json`
- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
- Verificato: `tools/exp_logistic_counter_scope_gate.py`
- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
- Inferito: il supporto e grammatica locale per questo perimetro perche la
  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.

exec
/bin/bash -lc 'git diff -- tools/exp_logistic_cyclic_block_entropy_gate.py tools/data/reports/agent_20260507_1419.md tools/data/seme.json' in /opt/MM_D-ND
 succeeded in 0ms:
diff --git a/tools/data/seme.json b/tools/data/seme.json
index db10e4e..750ca0c 100644
--- a/tools/data/seme.json
+++ b/tools/data/seme.json
@@ -38,7 +38,7 @@
     {
       "tipo": "vincolo",
       "id": "ORDER_DENOMINATOR_GATE",
-      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
+      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Nel perimetro cyclic-cut agent_20260507_1419, il residuo logistic_orbit_values non e artefatto del taglio lineare: cyclic_block_entropy_deficit_k4 e invariato sotto rotazione e replica contro marginal_shuffle e block_shuffle size 4-256 in run e seed check. Logistic_symbolic_itinerary e logistic_return_intervals restano blank nel criterio replicato. Il supporto logistic rimasto e orbit-local block grammar, non return/generating-partition support.",
       "intensita": 0.92,
       "porta": "META",
       "manuale": true,
@@ -309,4 +309,4 @@
       "timestamp": "2026-04-04T19:34:08.014Z"
     }
   ]
-}
\ No newline at end of file
+}

exec
/bin/bash -lc "python - <<'PY'
import json
from pathlib import Path
for p in ['tools/data/seme.json','tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json','tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json']:
    json.loads(Path(p).read_text())
    print(f'valid {p}')
PY" in /opt/MM_D-ND
 succeeded in 0ms:
valid tools/data/seme.json
valid tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
valid tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json

diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/data/seme.json b/tools/data/seme.json
index db10e4eb50cd5826f4a65ec6b037b68b83a3d42a..750ca0c31a52b3bdb98be23fd0c4ea3092bdee1e
--- a/tools/data/seme.json
+++ b/tools/data/seme.json
@@ -38,7 +38,7 @@
     {
       "tipo": "vincolo",
       "id": "ORDER_DENOMINATOR_GATE",
-      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
+      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Nel perimetro cyclic-cut agent_20260507_1419, il residuo logistic_orbit_values non e artefatto del taglio lineare: cyclic_block_entropy_deficit_k4 e invariato sotto rotazione e replica contro marginal_shuffle e block_shuffle size 4-256 in run e seed check. Logistic_symbolic_itinerary e logistic_return_intervals restano blank nel criterio replicato. Il supporto logistic rimasto e orbit-local block grammar, non return/generating-partition support.",
       "intensita": 0.92,
       "porta": "META",
       "manuale": true,
@@ -309,4 +309,4 @@
       "timestamp": "2026-04-04T19:34:08.014Z"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

apply patch
patch: completed
/opt/MM_D-ND/tools/data/seme.json
diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/data/seme.json b/tools/data/seme.json
index db10e4eb50cd5826f4a65ec6b037b68b83a3d42a..0e4d10f7ddac481d50c645b0c18ee5140d513b6d
--- a/tools/data/seme.json
+++ b/tools/data/seme.json
@@ -1,5 +1,5 @@
 {
-  "timestamp": "2026-05-07T10:49:04.273096",
+  "timestamp": "2026-05-07T14:19:00.000000",
   "piano": 75,
   "tensioni": [
     {
@@ -38,7 +38,7 @@
     {
       "tipo": "vincolo",
       "id": "ORDER_DENOMINATOR_GATE",
-      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
+      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Nel perimetro cyclic-cut agent_20260507_1419, il residuo logistic_orbit_values non e artefatto del taglio lineare: cyclic_block_entropy_deficit_k4 e invariato sotto rotazione e replica contro marginal_shuffle e block_shuffle size 4-256 in run e seed check. Logistic_symbolic_itinerary e logistic_return_intervals restano blank nel criterio replicato. Il supporto logistic rimasto e orbit-local block grammar, non return/generating-partition support.",
       "intensita": 0.92,
       "porta": "META",
       "manuale": true,
@@ -309,4 +309,4 @@
       "timestamp": "2026-04-04T19:34:08.014Z"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/data/seme.json b/tools/data/seme.json
index db10e4eb50cd5826f4a65ec6b037b68b83a3d42a..0e4d10f7ddac481d50c645b0c18ee5140d513b6d
--- a/tools/data/seme.json
+++ b/tools/data/seme.json
@@ -1,5 +1,5 @@
 {
-  "timestamp": "2026-05-07T10:49:04.273096",
+  "timestamp": "2026-05-07T14:19:00.000000",
   "piano": 75,
   "tensioni": [
     {
@@ -38,7 +38,7 @@
     {
       "tipo": "vincolo",
       "id": "ORDER_DENOMINATOR_GATE",
-      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
+      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Nel perimetro cyclic-cut agent_20260507_1419, il residuo logistic_orbit_values non e artefatto del taglio lineare: cyclic_block_entropy_deficit_k4 e invariato sotto rotazione e replica contro marginal_shuffle e block_shuffle size 4-256 in run e seed check. Logistic_symbolic_itinerary e logistic_return_intervals restano blank nel criterio replicato. Il supporto logistic rimasto e orbit-local block grammar, non return/generating-partition support.",
       "intensita": 0.92,
       "porta": "META",
       "manuale": true,
@@ -309,4 +309,4 @@
       "timestamp": "2026-04-04T19:34:08.014Z"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

exec
/bin/bash -lc 'ls -l tools/data/reports/agent_20260507_1419.md tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json' in /opt/MM_D-ND
 succeeded in 0ms:
-rw-r--r-- 1 root root 27667 May  7 14:21 tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
-rw-r--r-- 1 root root 27335 May  7 14:21 tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
-rw-r--r-- 1 root root  6415 May  7 14:22 tools/data/reports/agent_20260507_1419.md

exec
/bin/bash -lc 'git status --short' in /opt/MM_D-ND
 succeeded in 0ms:
 M applications/INDEX.draft.json
 M applications/soluzioni/20260430_0330_three-regimes-of-modular-memory-algebraic/finding_index.draft.json
 M applications/soluzioni/20260430_0330_three-regimes-of-modular-memory-algebraic/manifest.draft.json
 M tools/LAB_AGENT_CONTEXT.md
 M tools/data/agent_field_live.md
 M tools/data/conoscenza_teorie.json
 M tools/data/consecutio.json
 M tools/data/consecutio_processata.json
 M tools/data/domande_fondamentali.json
 M tools/data/incrocio_risultato.json
 M tools/data/lab_data.json
 M tools/data/lab_graph.json
 M tools/data/lab_health.json
 M tools/data/lab_session_log.jsonl
 M tools/data/pipeline_state.json
 M tools/data/ponti_evoluti.json
 M tools/data/refresh_detector_state.json
 M tools/data/seme.json
 D tools/data/seme_archive/piano_37.json
 D tools/data/seme_archive/piano_38.json
 D tools/data/seme_archive/piano_4.json
 D tools/data/seme_archive/piano_5.json
 D tools/data/seme_archive/piano_6.json
 M tools/data/seme_axioms.json
 M tools/data/seme_backup_pre_run.json
 M tools/data/tm1_figures/tensions.json
 M tools/data/tm1_figures/tensions_raw.json
?? applications/published/20260504_0901_the-two-markov-layers-are-coupled/
?? applications/published/20260504_1219_markov-layers-pass-the-first-recovery/
?? applications/published/20260505_0330_observable-rank-audit-many-probes-one/
?? applications/published/20260506_1955_observable-collinearity-breaks-only-where-denominators/
?? applications/published/20260507_0330_the-gue-poisson-boundary-is-a/
?? applications/published/20260507_0803_denominator-gate-transfers-boundary-coordinate-does/
?? applications/published/20260507_0942_bridge-order-denominator-gate/
?? applications/published/20260507_1006_logistic-counter-scope-gate/
?? applications/published/20260507_1042_logistic-surrogate-contract-gate/
?? applications/scoperte/20260502_0330_the-dipolar-phase-transition-is-nearly_auto/
?? applications/scoperte/20260503_0330_markov-memory-has-two-visible-layers_auto/
?? applications/scoperte/20260504_0901_the-two-markov-layers-are-coupled_auto/
?? applications/scoperte/20260504_1219_markov-layers-pass-the-first-recovery_auto/
?? applications/scoperte/20260505_0330_observable-rank-audit-many-probes-one_auto/
?? applications/scoperte/20260506_0625_perturbation-dimensionality-is-not-yet-a_auto/
?? applications/scoperte/20260506_1941_perturbation-rank-needs-denominator-gating_auto/
?? applications/scoperte/20260506_1955_observable-collinearity-breaks-only-where-denominators_auto/
?? applications/scoperte/20260507_0330_the-gue-poisson-boundary-is-a_auto/
?? applications/scoperte/20260507_0803_denominator-gate-transfers-boundary-coordinate-does_auto/
?? applications/scoperte/20260507_0901_denominator-gate-transfer-matrix_auto/
?? applications/scoperte/20260507_0923_semi-real-order-denominator-gate_auto/
?? applications/scoperte/20260507_0942_bridge-order-denominator-gate_auto/
?? applications/scoperte/20260507_1006_logistic-counter-scope-gate_auto/
?? applications/scoperte/20260507_1042_logistic-surrogate-contract-gate_auto/
?? applications/soluzioni/20260502_0330_the-dipolar-phase-transition-is-nearly/
?? applications/soluzioni/20260503_0330_markov-memory-has-two-visible-layers/
?? applications/soluzioni/20260504_0901_the-two-markov-layers-are-coupled/
?? applications/soluzioni/20260504_1219_markov-layers-pass-the-first-recovery/
?? applications/soluzioni/20260505_0330_observable-rank-audit-many-probes-one/
?? applications/soluzioni/20260506_1941_perturbation-rank-needs-denominator-gating/
?? applications/soluzioni/20260506_1955_observable-collinearity-breaks-only-where-denominators/
?? applications/soluzioni/20260507_0330_the-gue-poisson-boundary-is-a/
?? applications/soluzioni/20260507_0803_denominator-gate-transfers-boundary-coordinate-does/
?? applications/soluzioni/20260507_0942_bridge-order-denominator-gate/
?? applications/soluzioni/20260507_1006_logistic-counter-scope-gate/
?? applications/soluzioni/20260507_1042_logistic-surrogate-contract-gate/
?? findings_gallery.md
?? tools/data/.last_telegram_msg
?? tools/data/3d_boundary_layers.json
?? tools/data/aeternitas/
?? tools/data/alignment_active.json
?? tools/data/alignment_markers.jsonl
?? tools/data/bicono_projections.jsonl
?? tools/data/boundary_coherence.json
?? tools/data/boundary_mixture_gate_20260507_0330.json
?? tools/data/boundary_mixture_gate_20260507_0330_seedcheck.json
?? tools/data/boundary_shuffle_audit.json
?? tools/data/bridge_order_denominator_gate_20260507_0942.json
?? tools/data/bridge_order_denominator_gate_20260507_0942_seedcheck.json
?? tools/data/brody_calibration_results.json
?? tools/data/brody_flow.json
?? tools/data/conoscenza_teorie.json.bak.retraction_22_04
?? tools/data/cross_domain_dipolar_direction.json
?? tools/data/cross_observable_consistency.json
?? tools/data/crossover_phase_test.json
?? tools/data/denominator_gate_transfer_matrix.json
?? tools/data/dipolar_crossover.json
?? tools/data/dipolar_vector_scaling.json
?? tools/data/domandatore/domandatore_20260421_0746.json
?? tools/data/domandatore/domandatore_20260422_0345.json
?? tools/data/domandatore/domandatore_20260423_0345.json
?? tools/data/domandatore/domandatore_20260424_0345.json
?? tools/data/domandatore/domandatore_20260425_0345.json
?? tools/data/domandatore/domandatore_20260426_0345.json
?? tools/data/domandatore/domandatore_20260427_0345.json
?? tools/data/domandatore/domandatore_20260428_0345.json
?? tools/data/domandatore/domandatore_20260428_1236.json
?? tools/data/domandatore/domandatore_20260429_0345.json
?? tools/data/domandatore/domandatore_20260430_0345.json
?? tools/data/domandatore/domandatore_20260501_0345.json
?? tools/data/domandatore/domandatore_20260502_0345.json
?? tools/data/domandatore/domandatore_20260503_0345.json
?? tools/data/domandatore/domandatore_20260504_0345.json
?? tools/data/domandatore/domandatore_20260505_0345.json
?? tools/data/domandatore/domandatore_20260506_0345.json
?? tools/data/domandatore/domandatore_20260507_0345.json
?? tools/data/domandatore/domandatore_20260507_0728.json
?? tools/data/domandatore/domandatore_20260507_0742.json
?? tools/data/domandatore/domandatore_20260507_1420.json
?? tools/data/duality_gate_transfer_20260507_0803.json
?? tools/data/duality_gate_transfer_20260507_0803_seedcheck.json
?? tools/data/evolution/evolution_20260422_0330.md
?? tools/data/evolution/evolution_20260422_1616.md
?? tools/data/evolution/evolution_20260423_0330.md
?? tools/data/evolution/evolution_20260424_0330.md
?? tools/data/evolution/evolution_20260425_0330.md
?? tools/data/evolution/evolution_20260427_0330.md
?? tools/data/evolution/evolution_20260428_0330.md
?? tools/data/evolution/evolution_20260429_0852.md
?? tools/data/evolution/evolution_20260429_1013.md
?? tools/data/evolution/evolution_20260430_0330.md
?? tools/data/evolution/evolution_20260430_1905.md
?? tools/data/evolution/evolution_20260430_1946.md
?? tools/data/evolution/evolution_20260501_0330.md
?? tools/data/evolution/evolution_20260501_0725.md
?? tools/data/evolution/evolution_20260501_0931.md
?? tools/data/evolution/evolution_20260502_0330.md
?? tools/data/evolution/evolution_20260503_0330.md
?? tools/data/evolution/evolution_20260504_0901.md
?? tools/data/evolution/evolution_20260504_1219.md
?? tools/data/evolution/evolution_20260505_0330.md
?? tools/data/evolution/evolution_20260506_0330.md
?? tools/data/evolution/evolution_20260506_1941.md
?? tools/data/evolution/evolution_20260506_1955.md
?? tools/data/evolution/evolution_20260507_0330.md
?? tools/data/evolution/evolution_20260507_0803.md
?? tools/data/evolution/evolution_20260507_0901.md
?? tools/data/evolution/evolution_20260507_0923.md
?? tools/data/evolution/evolution_20260507_0942.md
?? tools/data/evolution/evolution_20260507_1006.md
?? tools/data/evolution/evolution_20260507_1042.md
?? tools/data/incrocio_20260422_0336.json
?? tools/data/incrocio_20260423_0335.json
?? tools/data/incrocio_20260424_0347.json
?? tools/data/incrocio_20260425_0339.json
?? tools/data/incrocio_20260428_0340.json
?? tools/data/incrocio_20260429_0859.json
?? tools/data/incrocio_20260506_0633.json
?? tools/data/lab_bridge_issues.jsonl
?? tools/data/logistic_counter_scope_gate_20260507_1006.json
?? tools/data/logistic_counter_scope_gate_20260507_1006_seedcheck.json
?? tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
?? tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
?? tools/data/logistic_surrogate_contract_gate_20260507_1042.json
?? tools/data/logistic_surrogate_contract_gate_20260507_1042_seedcheck.json
?? tools/data/magnitude_psd_from_acf.json
?? tools/data/markov3_observable_hunt.json
?? tools/data/markov_dipolar_decomposition.json
?? tools/data/markov_k_direction.json
?? tools/data/markov_layer_recovery_audit.json
?? tools/data/markov_memory_by_gue_type.json
?? tools/data/markov_scale_function.json
?? tools/data/meta_tautology_test.json
?? tools/data/mod3_scaling.json
?? tools/data/mod3_vs_residual_ordering.json
?? tools/data/modular_algebra_depth.json
?? tools/data/modular_memory_spectrum.json
?? tools/data/observable_collinearity_breaking_20260506_1955.json
?? tools/data/observable_collinearity_breaking_20260506_1956.json
?? tools/data/observable_collinearity_breaking_20260506_1957.json
?? tools/data/observable_rank_audit.json
?? tools/data/observable_rank_audit_seed20260506.json
?? tools/data/observatorio/domandatore_unTouched_20260507_095914.md
?? tools/data/observatorio/lazarus_cimitero_20260507_100015.md
?? tools/data/observatorio/meta_metodo_giro_2026-05-07.md
?? tools/data/perturbation_dimensionality_audit.json
?? tools/data/perturbation_dimensionality_audit_scale0330.json
?? tools/data/perturbation_rank_size_curve.json
?? tools/data/promotions/
?? tools/data/reports/_quarantine_falsifier_29_04/
?? tools/data/reports/agent_20260422_0330.md
?? tools/data/reports/agent_20260422_1616.md
?? tools/data/reports/agent_20260423_0330.md
?? tools/data/reports/agent_20260424_0330.md
?? tools/data/reports/agent_20260425_0330.md
?? tools/data/reports/agent_20260426_0330.md
?? tools/data/reports/agent_20260427_0330.md
?? tools/data/reports/agent_20260428_0330.md
?? tools/data/reports/agent_20260429_1013.md
?? tools/data/reports/agent_20260429_1041.md
?? tools/data/reports/agent_20260430_0330.md
?? tools/data/reports/agent_20260430_1905.md
?? tools/data/reports/agent_20260430_1919.md
?? tools/data/reports/agent_20260430_1946.md
?? tools/data/reports/agent_20260501_0330.md
?? tools/data/reports/agent_20260501_0725.md
?? tools/data/reports/agent_20260501_0931.md
?? tools/data/reports/agent_20260502_0330.md
?? tools/data/reports/agent_20260503_0330.md
?? tools/data/reports/agent_20260504_0901.md
?? tools/data/reports/agent_20260504_1219.md
?? tools/data/reports/agent_20260505_0330.md
?? tools/data/reports/agent_20260505_1022.md
?? tools/data/reports/agent_20260506_0330.md
?? tools/data/reports/agent_20260506_0625.md
?? tools/data/reports/agent_20260506_1941.md
?? tools/data/reports/agent_20260506_1955.md
?? tools/data/reports/agent_20260507_0330.md
?? tools/data/reports/agent_20260507_0803.md
?? tools/data/reports/agent_20260507_0901.md
?? tools/data/reports/agent_20260507_0923.md
?? tools/data/reports/agent_20260507_0942.md
?? tools/data/reports/agent_20260507_1006.md
?? tools/data/reports/agent_20260507_1042.md
?? tools/data/reports/agent_20260507_1419.md
?? tools/data/reports/ddf_20260422_0405.json
?? tools/data/reports/ddf_20260423_0405.json
?? tools/data/reports/ddf_20260424_0405.json
?? tools/data/reports/ddf_20260425_0405.json
?? tools/data/reports/ddf_20260426_0405.json
?? tools/data/reports/ddf_20260427_0405.json
?? tools/data/reports/ddf_20260428_0405.json
?? tools/data/reports/ddf_20260429_0405.json
?? tools/data/reports/ddf_20260430_0405.json
?? tools/data/reports/ddf_20260501_0405.json
?? tools/data/reports/ddf_20260502_0405.json
?? tools/data/reports/ddf_20260503_0405.json
?? tools/data/reports/ddf_20260504_0405.json
?? tools/data/reports/ddf_20260505_0405.json
?? tools/data/reports/ddf_20260505_0636.json
?? tools/data/reports/ddf_20260506_0405.json
?? tools/data/reports/ddf_20260507_0405.json
?? tools/data/reports/evolution_20260422_0330.md
?? tools/data/reports/evolution_20260422_1616.md
?? tools/data/reports/evolution_20260423_0330.md
?? tools/data/reports/evolution_20260424_0330.md
?? tools/data/reports/evolution_20260425_0330.md
?? tools/data/reports/evolution_20260427_0330.md
?? tools/data/reports/evolution_20260428_0330.md
?? tools/data/reports/evolution_20260503_0330.md
?? tools/data/reports/evolution_20260504_0330.md
?? tools/data/reports/evolution_20260505_0330.md
?? tools/data/reports/evolution_20260506_0330.md
?? tools/data/reports/evolution_20260506_1941.md
?? tools/data/reports/falsifier_20260429_1013.json
?? tools/data/reports/falsifier_20260429_1041.json
?? tools/data/reports/falsifier_20260430_0330.json
?? tools/data/reports/falsifier_20260430_1905.json
?? tools/data/reports/falsifier_20260430_1919.json
?? tools/data/reports/falsifier_20260430_1946.json
?? tools/data/reports/falsifier_20260501_0330.json
?? tools/data/reports/falsifier_20260501_0725.json
?? tools/data/reports/falsifier_20260501_0931.json
?? tools/data/reports/falsifier_20260502_0330.json
?? tools/data/reports/falsifier_20260503_0330.json
?? tools/data/reports/falsifier_20260504_0901.json
?? tools/data/reports/falsifier_20260504_1219.json
?? tools/data/reports/falsifier_20260505_0330.json
?? tools/data/reports/falsifier_20260506_0330.raw.txt
?? tools/data/reports/falsifier_20260506_0625.json
?? tools/data/reports/falsifier_20260506_1941.json
?? tools/data/reports/falsifier_20260506_1955.json
?? tools/data/reports/falsifier_20260506_1955.raw.txt
?? tools/data/reports/falsifier_20260507_0330.json
?? tools/data/reports/falsifier_20260507_0330.raw.txt
?? tools/data/reports/falsifier_20260507_0803.json
?? tools/data/reports/falsifier_20260507_0803.raw.txt
?? tools/data/reports/falsifier_20260507_0901.json
?? tools/data/reports/falsifier_20260507_0923.json
?? tools/data/reports/falsifier_20260507_0923.raw.txt
?? tools/data/reports/falsifier_20260507_0942.json
?? tools/data/reports/falsifier_20260507_1006.json
?? tools/data/reports/falsifier_20260507_1042.json
?? tools/data/reports/falsifier_20260507_1042.raw.txt
?? tools/data/reports/incident_20260504_0721.md
?? tools/data/reports/incident_20260504_1138.md
?? tools/data/reports/latest.md
?? tools/data/reports/loop_guard_20260507_0330.json
?? tools/data/reports/loop_guard_20260507_0803.json
?? tools/data/reports/loop_guard_20260507_0901.json
?? tools/data/reports/loop_guard_20260507_0923.json
?? tools/data/reports/loop_guard_20260507_0942.json
?? tools/data/reports/loop_guard_20260507_1006.json
?? tools/data/reports/loop_guard_20260507_1042.json
?? tools/data/reports/trajectory_apply_20260506_1931.json
?? tools/data/reports/trajectory_apply_20260506_1941.json
?? tools/data/reports/trajectory_apply_20260506_1955.json
?? tools/data/reports/trajectory_apply_20260507_0330.json
?? tools/data/reports/trajectory_apply_20260507_0803.json
?? tools/data/reports/trajectory_apply_20260507_0901.json
?? tools/data/reports/trajectory_apply_20260507_0923.json
?? tools/data/reports/trajectory_apply_20260507_0942.json
?? tools/data/reports/trajectory_apply_20260507_1006.json
?? tools/data/reports/trajectory_apply_20260507_1042.json
?? tools/data/scale_selective_perturbation.json
?? tools/data/selective_layer_decoupling.json
?? tools/data/seme_archive/piano_70.json
?? tools/data/seme_archive/piano_71.json
?? tools/data/seme_archive/piano_72.json
?? tools/data/seme_archive/piano_73.json
?? tools/data/seme_archive/piano_74.json
?? tools/data/semireal_order_denominator_gate_20260507_0923.json
?? tools/data/semireal_order_denominator_gate_20260507_0923_seedcheck.json
?? tools/data/spectral_rigidity_results.json
?? tools/data/trajectory_apply_history.jsonl
?? tools/data/two_channel_boundary.json
?? tools/data/two_channel_cross_domain.json
?? tools/data/two_channel_shuffle_audit.json
?? tools/data/two_layer_universality.json
?? tools/data/valutatore_log.jsonl
?? tools/data/veritas/
?? tools/evolution_report.md
?? tools/exp_3d_boundary_layers.py
?? tools/exp_boundary_coherence.py
?? tools/exp_boundary_mixture_gate.py
?? tools/exp_boundary_shuffle_audit.py
?? tools/exp_bridge_order_denominator_gate.py
?? tools/exp_brody_calibration.py
?? tools/exp_brody_flow.py
?? tools/exp_cross_domain_dipolar_direction.py
?? tools/exp_cross_observable_consistency.py
?? tools/exp_crossover_phase_test.py
?? tools/exp_denominator_gate_transfer_matrix.py
?? tools/exp_dipolar_angle_reference.py
?? tools/exp_dipolar_crossover.py
?? tools/exp_dipolar_vector_scaling.py
?? tools/exp_duality_gate_transfer.py
?? tools/exp_logistic_counter_scope_gate.py
?? tools/exp_logistic_cyclic_block_entropy_gate.py
?? tools/exp_logistic_surrogate_contract_gate.py
?? tools/exp_magnitude_psd_from_acf.py
?? tools/exp_markov3_observable_hunt.py
?? tools/exp_markov_dipolar_decomposition.py
?? tools/exp_markov_k_direction.py
?? tools/exp_markov_layer_recovery_audit.py
?? tools/exp_markov_memory_by_gue_type.py
?? tools/exp_markov_psd_prediction.py
?? tools/exp_markov_scale_function.py
?? tools/exp_meta_tautology_test.py
?? tools/exp_mod3_scaling.py
?? tools/exp_mod3_vs_residual_ordering.py
?? tools/exp_modular_algebra_depth.py
?? tools/exp_modular_memory_spectrum.py
?? tools/exp_observable_rank_audit.py
?? tools/exp_perturbation_dimensionality_audit.py
?? tools/exp_perturbation_rank_size_curve.py
?? tools/exp_scale_selective_perturbation.py
?? tools/exp_selective_layer_decoupling.py
?? tools/exp_semireal_order_denominator_gate.py
?? tools/exp_spectral_rigidity.py
?? tools/exp_two_channel_boundary.py
?? tools/exp_two_channel_cross_domain.py
?? tools/exp_two_channel_shuffle_audit.py
?? tools/exp_two_layer_universality.py
?? tools/observables_registry.py

exec
/bin/bash -lc "python - <<'PY'
import json
from pathlib import Path
json.loads(Path('tools/data/seme.json').read_text())
print('valid tools/data/seme.json')
PY" in /opt/MM_D-ND
 succeeded in 0ms:
valid tools/data/seme.json

diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/data/seme.json b/tools/data/seme.json
index db10e4eb50cd5826f4a65ec6b037b68b83a3d42a..0e4d10f7ddac481d50c645b0c18ee5140d513b6d
--- a/tools/data/seme.json
+++ b/tools/data/seme.json
@@ -1,5 +1,5 @@
 {
-  "timestamp": "2026-05-07T10:49:04.273096",
+  "timestamp": "2026-05-07T14:19:00.000000",
   "piano": 75,
   "tensioni": [
     {
@@ -38,7 +38,7 @@
     {
       "tipo": "vincolo",
       "id": "ORDER_DENOMINATOR_GATE",
-      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
+      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Nel perimetro cyclic-cut agent_20260507_1419, il residuo logistic_orbit_values non e artefatto del taglio lineare: cyclic_block_entropy_deficit_k4 e invariato sotto rotazione e replica contro marginal_shuffle e block_shuffle size 4-256 in run e seed check. Logistic_symbolic_itinerary e logistic_return_intervals restano blank nel criterio replicato. Il supporto logistic rimasto e orbit-local block grammar, non return/generating-partition support.",
       "intensita": 0.92,
       "porta": "META",
       "manuale": true,
@@ -309,4 +309,4 @@
       "timestamp": "2026-04-04T19:34:08.014Z"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

codex
Eseguito il cycle e scritto il report in [agent_20260507_1419.md](/opt/MM_D-ND/tools/data/reports/agent_20260507_1419.md).

Ho aggiunto lo strumento riusabile [exp_logistic_cyclic_block_entropy_gate.py](/opt/MM_D-ND/tools/exp_logistic_cyclic_block_entropy_gate.py) e i due depositi dati:
[main json](/opt/MM_D-ND/tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json), [seed check](/opt/MM_D-ND/tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json).

Finding: il residuo `logistic_orbit_values / block_entropy_deficit_k4` non cade come artefatto del taglio lineare. La misura ciclica resta invariata sotto rotazione e replica contro `marginal_shuffle` e `block_shuffle` size `4-256` in run e seed check. `logistic_symbolic_itinerary` e `logistic_return_intervals` restano blank nel criterio replicato.

Ho aggiornato `tools/data/seme.json` su `ORDER_DENOMINATOR_GATE` e validato:
`py_compile` dello script nuovo, JSON main/seed/seme validi.
diff --git a/tools/data/reports/agent_20260507_1419.md b/tools/data/reports/agent_20260507_1419.md
new file mode 100644
index 0000000000000000000000000000000000000000..0129567f09ab0e2b0ad891144344b50bdd064bce
--- /dev/null
+++ b/tools/data/reports/agent_20260507_1419.md
@@ -0,0 +1,148 @@
+# Agent Report — Logistic Cyclic Block Entropy Gate
+
+timestamp: 2026-05-07 14:19 UTC
+category: meta_cut_artifact_falsification
+verdict: orbit_local_grammar_not_linear_cut
+observables_registry: not used for canonical observables
+observables_native_version: logistic-native-1.0.0-2026-05-07
+observables_cyclic_version: logistic-cyclic-block-entropy-1.0.0-2026-05-07
+observables_used: [linear_block_entropy_deficit_k4, cyclic_block_entropy_deficit_k4]
+tool: tools/exp_logistic_cyclic_block_entropy_gate.py
+data: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json
+seed_check: tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json
+
+## Claim Under Test
+
+Verificato dal seme: il residuo del run 10:42 era
+`logistic_orbit_values / block_entropy_deficit_k4`. Il denominatore
+`circular_shift` era piccolo; il nodo regressivo e distinguere grammatica
+locale dell'orbita da artefatto del taglio lineare.
+
+Questo run confronta:
+
+- `linear_block_entropy_deficit_k4`: osservabile precedente, non-wrapping.
+- `cyclic_block_entropy_deficit_k4`: include i blocchi che attraversano il wrap;
+  una rotazione circolare non cambia l'osservabile.
+- `block_shuffle` con block size `[4, 8, 16, 32, 64, 128, 256]`.
+
+Regola del gate: il supporto ciclico replica solo se `abs(z)>=2` contro
+`marginal_shuffle` e contro tutti i block size dichiarati, con
+`cyclic_block_entropy_deficit_k4` invariato sotto rotazione.
+
+## Deposito Numerico
+
+Run principale: `n_values=4096`, `n_returns=4096`, `n_baseline=32`,
+`n_rotations=32`, `seed=202605071419`.
+
+Seed check: `n_baseline=28`, `n_rotations=28`, `seed=202605071420`.
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | linear, cyclic | cyclic | 4, 256 |
+| logistic_return_intervals | [] | cyclic | 16 |
+
+Seed check:
+
+| perimeter | marginal stable | cyclic rotation invariant | cyclic block-shuffle support sizes |
+|---|---|---|---|
+| logistic_orbit_values | linear, cyclic | cyclic | 4, 8, 16, 32, 64, 128, 256 |
+| logistic_symbolic_itinerary | [] | cyclic | [] |
+| logistic_return_intervals | [] | cyclic | [] |
+
+Orbit values, run principale:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 725.413 | 732.167 |
+| rotation max_abs_delta | 0.000220035 | 2.22e-16 |
+| block_shuffle size 4 z | 53.629 | 53.913 |
+| block_shuffle size 8 z | 41.841 | 41.017 |
+| block_shuffle size 16 z | 22.171 | 22.309 |
+| block_shuffle size 32 z | 10.781 | 10.884 |
+| block_shuffle size 64 z | 8.183 | 8.077 |
+| block_shuffle size 128 z | 7.350 | 6.709 |
+| block_shuffle size 256 z | 3.949 | 3.798 |
+
+Orbit values, seed check:
+
+| test | linear z / delta | cyclic z / delta |
+|---|---:|---:|
+| marginal_shuffle z | 474.877 | 469.664 |
+| rotation max_abs_delta | 0.000802208 | 1.11e-16 |
+| block_shuffle size 4 z | 55.186 | 55.518 |
+| block_shuffle size 8 z | 30.808 | 31.162 |
+| block_shuffle size 16 z | 24.176 | 24.414 |
+| block_shuffle size 32 z | 12.214 | 11.993 |
+| block_shuffle size 64 z | 8.861 | 8.386 |
+| block_shuffle size 128 z | 5.592 | 5.130 |
+| block_shuffle size 256 z | 4.832 | 4.086 |
+
+## Risultato
+
+1. **The orbit support is local grammar, not the linear starting cut.**
+
+   `cyclic_block_entropy_deficit_k4` is invariant under circular rotation
+   (`max_abs_delta <= 2.22e-16`) and stays above gate against marginal shuffle
+   and every declared block-shuffle size in both runs.
+
+2. **The support weakens with larger preserved blocks but stays inside gate.**
+
+   For `logistic_orbit_values`, cyclic z decreases from `53.913` to `3.798`
+   in the main run and from `55.518` to `4.086` in seed check as block size
+   moves from `4` to `256`. The block-size scan does not make the support blank
+   in this perimetro.
+
+3. **The generating partition and return intervals remain counter-scope.**
+
+   `logistic_symbolic_itinerary` and `logistic_return_intervals` produce
+   non-replicated hits in the main run, then blank support in seed check. Their
+   cyclic rotation invariance is a property of the observable construction, not
+   evidence of logistic support.
+
+## Consecutio
+
+`ORDER_DENOMINATOR_GATE` narrows without collapsing:
+
+> The logistic counter-scope keeps one replicated object: orbit-local block
+> grammar. The object is cyclic/start-invariant and survives block-shuffle
+> scans from 4 to 256 samples. The generating itinerary and return intervals
+> remain blank.
+
+Next experiment: move from entropy deficit to explicit block-transition graph.
+The discriminant is whether the surviving grammar lives in short word counts
+alone or in directed transitions between words.
+
+## Self-Audit: 5 Lenti
+
+L1 hard constraint vs bias: "blank" is used only for replicated support under
+the declared gate. Main-run hits for itinerary and return intervals are reported
+and excluded because seed check removes them.
+
+L2 quantity vs ratio: raw z values and rotation deltas are reported. The claim
+uses `abs(z)>=2`, not percentages across unequal denominators.
+
+L3 no silent patching: the 10:42 claim is not replaced. The previous surviving
+observable is tested at its stated weak node: circular cut sensitivity.
+
+L4 edge cases: itinerary support at block sizes `4` and `256`, and return support
+at block size `16`, are kept in the table and not generalized.
+
+L5 re-discovery vs discovery: logistic-map symbolic dynamics is classical. This
+report claims a lab-gate scope for a native observable, not a new theorem about
+the logistic map.
+
+## Fonti
+
+- Verificato: `tools/data/agent_field_live.md`
+- Verificato: `tools/LAB_AGENT_CONTEXT.md`
+- Verificato: `tools/data/seme.json`
+- Verificato: `shared_memory/feedback_indeterminato_reitera_con_sistema_2026-05-07.md`
+- Verificato: `tools/exp_logistic_counter_scope_gate.py`
+- Verificato: `tools/exp_logistic_surrogate_contract_gate.py`
+- Verificato: `tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json`
+- Verificato: `tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json`
+- Verificato: `python -m py_compile tools/exp_logistic_cyclic_block_entropy_gate.py`
+- Inferito: il supporto e grammatica locale per questo perimetro perche la
+  misura ciclica elimina il taglio di partenza e replica contro block-shuffle.
diff --git a/tools/data/seme.json b/tools/data/seme.json
index db10e4eb50cd5826f4a65ec6b037b68b83a3d42a..0e4d10f7ddac481d50c645b0c18ee5140d513b6d
--- a/tools/data/seme.json
+++ b/tools/data/seme.json
@@ -1,5 +1,5 @@
 {
-  "timestamp": "2026-05-07T10:49:04.273096",
+  "timestamp": "2026-05-07T14:19:00.000000",
   "piano": 75,
   "tensioni": [
     {
@@ -38,7 +38,7 @@
     {
       "tipo": "vincolo",
       "id": "ORDER_DENOMINATOR_GATE",
-      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Il supporto logistic rimasto e orbit-block-entropy, non return/generating-partition support. Circular-shift z usa denominatori molto piccoli: il prossimo nodo e separare grammatica locale da artefatto del taglio lineare.",
+      "claim": "Il denominator gate trasferisce come supporto one-sided dell'ordine quando l'ordine e visibile agli osservabili del perimetro, non come endpoint-stable support a due poli. Nel perimetro sintetico agent_20260507_0901, 4/4 domini non-BOUNDARY hanno endpoint_stable_observables=[] e polo coerente stable_count 3.0-5.0. Nel perimetro semi-reale agent_20260507_0923, primi e zeta trasferiscono (primi: SR,L1,triple_var; zeta: SR,L2), ma logistic_return_intervals e blank: stable_count coerente 0.0-0.2. Nel perimetro bridge agent_20260507_0942, prime_metric_delta_gamma_abs, prime_metric_dR_abs, zeta_trace_residual_step5_abs e hydrogen_bound_level_spacings trasferiscono su tutti i 5 osservabili canonici con endpoint_stable_observables=[]; e supporto perimetro-bridge, non universalita del gate. Nel perimetro logistic-native agent_20260507_1006, logistic_orbit_values trasferisce su block_entropy_deficit_k4 in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals mostra recurrence_diag_mean solo nel run principale e torna blank nel seed check. La beta 0.10/0.30/0.40/0.50 resta coordinata del protocollo quando compare, non coordinata universale. Nel perimetro surrogate-contract agent_20260507_1042, logistic_orbit_values trasferisce solo tramite block_entropy_deficit_k4 e sopravvive a marginal_shuffle, circular_shift e block_shuffle in run e seed check; logistic_symbolic_itinerary resta blank; logistic_return_intervals non replica (recurrence_diag_mean compare contro marginal/block nel run principale ma sparisce nel seed check). Nel perimetro cyclic-cut agent_20260507_1419, il residuo logistic_orbit_values non e artefatto del taglio lineare: cyclic_block_entropy_deficit_k4 e invariato sotto rotazione e replica contro marginal_shuffle e block_shuffle size 4-256 in run e seed check. Logistic_symbolic_itinerary e logistic_return_intervals restano blank nel criterio replicato. Il supporto logistic rimasto e orbit-local block grammar, non return/generating-partition support.",
       "intensita": 0.92,
       "porta": "META",
       "manuale": true,
@@ -309,4 +309,4 @@
       "timestamp": "2026-04-04T19:34:08.014Z"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tools/exp_logistic_cyclic_block_entropy_gate.py b/tools/exp_logistic_cyclic_block_entropy_gate.py
new file mode 100644
index 0000000000000000000000000000000000000000..bba58bad7bc1924f03720641cc323824d55a8e04
--- /dev/null
+++ b/tools/exp_logistic_cyclic_block_entropy_gate.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python3
+"""
+exp_logistic_cyclic_block_entropy_gate.py
+
+Falsification node after the logistic surrogate-contract gate.
+
+The 10:42 cycle left one surviving object:
+`logistic_orbit_values / block_entropy_deficit_k4`.  Circular-shift
+denominators were very small, so this tool separates local grammar from the
+linear starting-cut artifact:
+
+- linear block entropy: the existing non-wrapping observable;
+- cyclic block entropy: wrapping blocks are included, so circular shifts leave
+  the observable unchanged up to numerical precision;
+- block-shuffle scan: tests whether support survives when local chunks are
+  preserved but chunk order is broken at different block sizes.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from collections import Counter
+from pathlib import Path
+
+import numpy as np
+
+from exp_logistic_counter_scope_gate import (
+    OBSERVABLES_NATIVE_VERSION,
+    block_entropy_deficit,
+    logistic_orbit,
+    logistic_return_intervals,
+    logistic_symbolic_itinerary,
+    quantile_symbols,
+)
+from exp_logistic_surrogate_contract_gate import block_shuffle, circular_shift
+
+
+OBSERVABLES_CYCLIC_VERSION = "logistic-cyclic-block-entropy-1.0.0-2026-05-07"
+OBS_NAMES = ["linear_block_entropy_deficit_k4", "cyclic_block_entropy_deficit_k4"]
+PERIMETERS = ["logistic_orbit_values", "logistic_symbolic_itinerary", "logistic_return_intervals"]
+
+
+def cyclic_block_entropy_deficit(values: np.ndarray, k: int = 4, bins: int = 4) -> float:
+    symbols = quantile_symbols(values, bins)
+    n = len(symbols)
+    if n < k + 1:
+        return 0.0
+    alphabet = max(2, int(np.max(symbols)) + 1)
+    blocks = [tuple(symbols[(i + j) % n] for j in range(k)) for i in range(n)]
+    counts = np.array(list(Counter(blocks).values()), dtype=float)
+    probs = counts / float(np.sum(counts))
+    entropy = -float(np.sum(probs * np.log2(probs)))
+    max_entropy = k * math.log2(alphabet)
+    return float(max(0.0, 1.0 - entropy / max_entropy)) if max_entropy > 1e-15 else 0.0
+
+
+def compute(values: np.ndarray, k: int = 4, bins: int = 4) -> dict[str, float]:
+    return {
+        "linear_block_entropy_deficit_k4": block_entropy_deficit(values, k=k, bins=bins),
+        "cyclic_block_entropy_deficit_k4": cyclic_block_entropy_deficit(values, k=k, bins=bins),
+    }
+
+
+def z_against(values: np.ndarray, maker, n_baseline: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    baseline = {name: [] for name in OBS_NAMES}
+    for _ in range(n_baseline):
+        surrogate = maker(values, rng)
+        row = compute(surrogate)
+        for name in OBS_NAMES:
+            baseline[name].append(row[name])
+
+    means = {}
+    sds = {}
+    z = {}
+    for name in OBS_NAMES:
+        vals = np.array(baseline[name], dtype=float)
+        means[name] = float(np.mean(vals))
+        sds[name] = float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0
+        z[name] = float((original[name] - means[name]) / sds[name]) if sds[name] > 1e-15 else 0.0
+    return {"original": original, "baseline_mean": means, "baseline_std": sds, "z": z}
+
+
+def rotation_sensitivity(values: np.ndarray, n_rotations: int, rng: np.random.Generator) -> dict:
+    original = compute(values)
+    rows = {name: [] for name in OBS_NAMES}
+    for _ in range(n_rotations):
+        row = compute(circular_shift(values, rng))
+        for name in OBS_NAMES:
+            rows[name].append(row[name])
+
+    out = {}
+    for name in OBS_NAMES:
+        vals = np.array(rows[name], dtype=float)
+        out[name] = {
+            "original": original[name],
+            "rotation_mean": float(np.mean(vals)),
+            "rotation_std": float(np.std(vals, ddof=1)) if len(vals) > 1 else 0.0,
+            "max_abs_delta": float(np.max(np.abs(vals - original[name]))) if len(vals) else 0.0,
+        }
+    return out
+
+
+def build_sequences(args: argparse.Namespace, rng: np.random.Generator) -> dict[str, np.ndarray]:
+    return {
+        "logistic_orbit_values": logistic_orbit(args.n_values, rng),
+        "logistic_symbolic_itinerary": logistic_symbolic_itinerary(args.n_values, rng),
+        "logistic_return_intervals": logistic_return_intervals(args.n_returns, rng),
+    }
+
+
+def analyze(values: np.ndarray, args: argparse.Namespace, rng: np.random.Generator) -> dict:
+    marginal = z_against(values, lambda v, r: r.permutation(v), args.n_baseline, rng)
+    rotation = rotation_sensitivity(values, args.n_rotations, rng)
+    blocks = {}
+    for block_size in args.block_sizes:
+        blocks[str(block_size)] = z_against(
+            values,
+            lambda v, r, bs=block_size: block_shuffle(v, bs, r),
+            args.n_baseline,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    cyclic_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["cyclic_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+    linear_block_support = [
+        int(block_size)
+        for block_size in args.block_sizes
+        if abs(blocks[str(block_size)]["z"]["linear_block_entropy_deficit_k4"]) >= args.z_min
+    ]
+
+    return {
+        "source": {
+            "n": int(len(values)),
+            "mean": float(np.mean(values)),
+            "variance": float(np.var(values)),
+            "unique_values": int(len(np.unique(values))),
+        },
+        "marginal_shuffle": marginal,
+        "rotation_sensitivity": rotation,
+        "block_shuffle_scan": blocks,
+        "support_summary": {
+            "marginal_stable": [
+                name for name in OBS_NAMES if abs(marginal["z"][name]) >= args.z_min
+            ],
+            "rotation_invariant": [
+                name
+                for name in OBS_NAMES
+                if rotation[name]["max_abs_delta"] <= args.rotation_eps
+            ],
+            "linear_block_shuffle_support_sizes": linear_block_support,
+            "cyclic_block_shuffle_support_sizes": cyclic_block_support,
+            "cyclic_support_all_declared_block_sizes": cyclic_block_support == list(args.block_sizes),
+        },
+    }
+
+
+def compact(perimeters: dict) -> dict:
+    out = {}
+    for name, data in perimeters.items():
+        out[name] = {
+            "n": data["source"]["n"],
+            "marginal_stable": data["support_summary"]["marginal_stable"],
+            "rotation_invariant": data["support_summary"]["rotation_invariant"],
+            "linear_block_shuffle_support_sizes": data["support_summary"]["linear_block_shuffle_support_sizes"],
+            "cyclic_block_shuffle_support_sizes": data["support_summary"]["cyclic_block_shuffle_support_sizes"],
+            "cyclic_support_all_declared_block_sizes": data["support_summary"][
+                "cyclic_support_all_declared_block_sizes"
+            ],
+            "marginal_z": data["marginal_shuffle"]["z"],
+            "rotation_max_abs_delta": {
+                obs: row["max_abs_delta"] for obs, row in data["rotation_sensitivity"].items()
+            },
+            "block_shuffle_z": {
+                block_size: row["z"] for block_size, row in data["block_shuffle_scan"].items()
+            },
+        }
+    return out
+
+
+def run(args: argparse.Namespace) -> dict:
+    rng = np.random.default_rng(args.seed)
+    sequences = build_sequences(args, rng)
+    perimeters = {}
+    for name in PERIMETERS:
+        perimeters[name] = analyze(
+            sequences[name],
+            args,
+            np.random.default_rng(rng.integers(0, 2**63 - 1)),
+        )
+
+    output = {
+        "experiment": "logistic_cyclic_block_entropy_gate",
+        "category": "meta_cut_artifact_falsification",
+        "question": "Does logistic orbit block-entropy support survive a cyclic/start-invariant observable and block-size scan?",
+        "observables_registry": "not used for canonical observables",
+        "observables_native_version": OBSERVABLES_NATIVE_VERSION,
+        "observables_cyclic_version": OBSERVABLES_CYCLIC_VERSION,
+        "observables_used": OBS_NAMES,
+        "params": {
+            **vars(args),
+            "block_sizes": list(args.block_sizes),
+        },
+        "matrix": compact(perimeters),
+        "perimeters": perimeters,
+    }
+
+    out_path = Path(args.out)
+    out_path.parent.mkdir(parents=True, exist_ok=True)
+    with out_path.open("w") as f:
+        json.dump(output, f, indent=2)
+
+    print(f"observables_native_version={OBSERVABLES_NATIVE_VERSION}")
+    print(f"observables_cyclic_version={OBSERVABLES_CYCLIC_VERSION}")
+    print(f"observables_used={OBS_NAMES}")
+    print("perimeter n marginal_stable rotation_invariant cyclic_block_sizes")
+    for name, row in output["matrix"].items():
+        print(
+            f"{name:>29s} "
+            f"{row['n']:>5d} "
+            f"{','.join(row['marginal_stable']) or '[]':>62s} "
+            f"{','.join(row['rotation_invariant']) or '[]':>62s} "
+            f"{row['cyclic_block_shuffle_support_sizes']}"
+        )
+    print(f"saved {out_path}")
+    return output
+
+
+def parse_block_sizes(raw: str) -> tuple[int, ...]:
+    values = tuple(int(item.strip()) for item in raw.split(",") if item.strip())
+    if not values:
+        raise argparse.ArgumentTypeError("at least one block size is required")
+    if any(value < 2 for value in values):
+        raise argparse.ArgumentTypeError("block sizes must be >= 2")
+    return values
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--n-values", type=int, default=4096)
+    parser.add_argument("--n-returns", type=int, default=4096)
+    parser.add_argument("--n-baseline", type=int, default=32)
+    parser.add_argument("--n-rotations", type=int, default=32)
+    parser.add_argument("--block-sizes", type=parse_block_sizes, default=(4, 8, 16, 32, 64, 128, 256))
+    parser.add_argument("--z-min", type=float, default=2.0)
+    parser.add_argument("--rotation-eps", type=float, default=1e-12)
+    parser.add_argument("--seed", type=int, default=202605071419)
+    parser.add_argument("--out", default="tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json")
+    args = parser.parse_args()
+    run(args)
+
+
+if __name__ == "__main__":
+    main()

tokens used
92,860
Eseguito il cycle e scritto il report in [agent_20260507_1419.md](/opt/MM_D-ND/tools/data/reports/agent_20260507_1419.md).

Ho aggiunto lo strumento riusabile [exp_logistic_cyclic_block_entropy_gate.py](/opt/MM_D-ND/tools/exp_logistic_cyclic_block_entropy_gate.py) e i due depositi dati:
[main json](/opt/MM_D-ND/tools/data/logistic_cyclic_block_entropy_gate_20260507_1419.json), [seed check](/opt/MM_D-ND/tools/data/logistic_cyclic_block_entropy_gate_20260507_1419_seedcheck.json).

Finding: il residuo `logistic_orbit_values / block_entropy_deficit_k4` non cade come artefatto del taglio lineare. La misura ciclica resta invariata sotto rotazione e replica contro `marginal_shuffle` e `block_shuffle` size `4-256` in run e seed check. `logistic_symbolic_itinerary` e `logistic_return_intervals` restano blank nel criterio replicato.

Ho aggiornato `tools/data/seme.json` su `ORDER_DENOMINATOR_GATE` e validato:
`py_compile` dello script nuovo, JSON main/seed/seme validi.
