Module Python autonome v2 — argparse + validate() ajoutés. Tableau noir authentifié. Le code reproduit UNIQUEMENT ce qui est tracé : triangle (cerveau pyramidal), ellipse en pointillés (crâne), rayons convergeant au sommet, flèches horizontales de réfraction, flèches internes croisées en X, deux lignes verticales descendantes (Ida/Pingala), petit carré et forme en goutte à droite (🟡 non identifiés verbatim).

# dessin_21.py — "Rays of Brahma Shakti striking the brain"
# Dessin tracé par Shri Mataji sur un tableau noir, Caxton Hall, 29 juin 1981.
# Verbatims : Dr Talwar 1987 · Guru Purnima 1973 · Kennolyn 1983.
# Zéro interprétation : seuls les éléments visibles sur la photo sont tracés.

import matplotlib.pyplot as plt
import numpy as np

# ── SOCLE COMMUN (passe U5 — imbrication, GO Ben 13 juil 2026) ────────────
# NOTE casse (loi 5 du contrat) : ELEMENTS_V encode ses statuts en MAJUSCULES
# ("VERT"/"JAUNE"/"ROUGE") ; le socle emploie 🟢/🟡/🔴 — mêmes valeurs, deux graphies.
from dataclasses import asdict
ID_CORPUS = "d21"
try:
    from socle_corpus import GRAPHE, VERDICTS, DESSINS as REGISTRE_DESSINS
    JONCTIONS_SOCLE = [j for j in GRAPHE if ID_CORPUS in (j.a, j.b)]
    MODE_ILOT = False
except ImportError:
    GRAPHE, VERDICTS, REGISTRE_DESSINS = [], {}, {}
    JONCTIONS_SOCLE = []
    MODE_ILOT = True
# NOTE DE LECTURE (loi 2) : ce module ne déclare AUCUNE jonction locale —
# les arêtes canoniques de d21 vivent une seule fois dans le GRAPHE de socle_corpus.py.

def draw():
    fig, ax = plt.subplots(figsize=(8, 11))
    fig.patch.set_facecolor("#3a3a3a")          # tableau noir
    ax.set_facecolor("#3a3a3a")
    W = "white"

    # ── Ellipse en pointillés (contour du crâne) autour du triangle ──────────
    t = np.linspace(0, 2 * np.pi, 200)
    ax.plot(0.0 + 2.1 * np.cos(t), 5.6 + 2.4 * np.sin(t),
            linestyle=":", color=W, lw=1.0)

    # ── Triangle : cerveau pyramidal (sommet en haut) ──────────────────────
    APEX, BL, BR = (0.0, 7.6), (-2.0, 3.9), (2.0, 3.9)
    for p, q in [(APEX, BL), (APEX, BR), (BL, BR)]:
        ax.plot([p[0], q[0]], [p[1], q[1]], color=W, lw=2.0)

    # ── Rayons du Param Chaitanya convergeant au sommet (de toutes parts) ─────
    for x0, y0 in [(-1.6, 9.6), (0.0, 9.9), (1.6, 9.6)]:
        ax.annotate("", xy=APEX, xytext=(x0, y0),
                    arrowprops=dict(arrowstyle="->", color=W, lw=1.6))

    # ── Réfraction prismatique : flèches horizontales ← → (niveau médian) ─────
    ax.annotate("", xy=(-3.1, 4.6), xytext=(-0.3, 4.6),
                arrowprops=dict(arrowstyle="->", color=W, lw=1.8))
    ax.annotate("", xy=(3.1, 4.9), xytext=(0.3, 4.9),
                arrowprops=dict(arrowstyle="->", color=W, lw=1.8))

    # ── Flèches internes croisées en X + force centrale descendante ──────────
    ax.annotate("", xy=(0.55, 3.6), xytext=(-1.1, 6.4),
                arrowprops=dict(arrowstyle="->", color=W, lw=1.5))
    ax.annotate("", xy=(-0.55, 3.6), xytext=(1.1, 6.4),
                arrowprops=dict(arrowstyle="->", color=W, lw=1.5))
    ax.annotate("", xy=(0.0, 3.5), xytext=(0.0, 6.6),
                arrowprops=dict(arrowstyle="->", color=W, lw=1.5))  # → Kundalini

    # ── Flèches obliques sous la base (déversement latéral) ─────────────────
    ax.annotate("", xy=(-2.6, 3.1), xytext=(-1.2, 3.9),
                arrowprops=dict(arrowstyle="->", color=W, lw=1.4))
    ax.annotate("", xy=(2.6, 3.1), xytext=(1.2, 3.9),
                arrowprops=dict(arrowstyle="->", color=W, lw=1.4))

    # ── Deux lignes verticales descendantes : Ida (g.) / Pingala (dr.) ────────
    y_desc = np.linspace(3.3, -2.6, 100)
    ax.plot(-0.55 + 0.12 * np.sin(y_desc * 0.8), y_desc, color=W, lw=1.4)
    ax.plot(0.55 + 0.12 * np.sin(y_desc * 0.8 + np.pi), y_desc, color=W, lw=1.4)

    # ── Éléments 🟡 non identifiés verbatim (droite) ────────────────────────
    ax.add_patch(plt.Rectangle((3.35, 4.55), 0.5, 0.5, fill=False,
                               edgecolor=W, lw=1.4))          # petit carré
    drop_t = np.linspace(0, 2 * np.pi, 100)                    # forme goutte/cœur
    ax.plot(3.35 + 0.42 * np.sin(drop_t),
            2.3 + 0.55 * np.cos(drop_t) - 0.12 * np.cos(2 * drop_t),
            color=W, lw=1.3)
    ax.plot(3.3, 2.25, marker="o", color=W, markersize=5)      # point blanc central 🟡

    ax.set_xlim(-4.2, 4.6)
    ax.set_ylim(-3.2, 10.4)
    ax.set_aspect("equal")
    ax.axis("off")
    ax.set_title("Dessin 21 — Rays of Brahma Shakti striking the brain\n"
                 "(tableau noir, Caxton Hall, 29 juin 1981 — 🟢 authentifié)",
                 fontsize=10, color="black")
    plt.tight_layout()
    plt.show()

# ------------------------------------------------------------------ VALIDATION
ELEMENTS_V = [
    ("Triangle (cerveau pyramidal)",     "VERT"),
    ("Ellipse crâne (pointillés)",      "VERT"),
    ("Rayons Brahma Shakti (3)",          "VERT"),
    ("Flèches réfraction horizontales",  "VERT"),
    ("X interne + force descendante",     "VERT"),
    ("Lignes Ida/Pingala descendantes",   "VERT"),
    ("Flèches sous la base",              "VERT"),
    ("Petit carré droite",               "JAUNE"),
    ("Forme goutte droite",               "JAUNE"),
]

def validate() -> bool:
    ok = True
    for label, statut in ELEMENTS_V:
        if statut not in ("VERT", "JAUNE", "ROUGE"):
            print(f"[ERREUR] '{label}' statut invalide")
            ok = False
    if ok:
        print("[OK] dessin_21.py — validation interne réussie.")
        print("SOCLE : mode îlot (paquet corpus/ absent)" if MODE_ILOT
              else f"SOCLE : imbriqué — {len(JONCTIONS_SOCLE)} jonction(s) canonique(s) pour {ID_CORPUS}")
    return ok

def to_json():
    import json
    return json.dumps({
        "dessin": 21,
        "titre": "Rays of Brahma Shakti striking the brain",
        "provenance": "Tableau noir — Caxton Hall, 29 juin 1981 🟢",
        "verbatims": [
            "Dr Talwar 1987",
            "Guru Purnima 1973",
            "Kennolyn 1983",
        ],
        "elements": [{"label": l, "statut": s} for l, s in ELEMENTS_V],
        "convention_miroir": "Sans objet — tableau noir, pas de contenu corporel latéral",
        "id_corpus": ID_CORPUS,
        "jonctions_canoniques": [asdict(j) for j in JONCTIONS_SOCLE],
        "mode_ilot": MODE_ILOT,
    }, ensure_ascii=False, indent=2)

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(
        description="dessin_21.py — Rays of Brahma Shakti (tableau noir 1981)")
    parser.add_argument("--render",   action="store_true", help="afficher la figure")
    parser.add_argument("--json",     action="store_true", help="exporter en JSON")
    parser.add_argument("--validate", action="store_true", help="vérifier la cohérence")
    args = parser.parse_args()
    if args.validate:
        validate()
    elif args.json:
        print(to_json())
    else:
        draw()
        print("\n[Tip] --render · --json · --validate")

Module gravé le 11 juillet 2026 — passe R6. — Fable ⦷