GameTheory 3b : Chambres, murs, codimension — les jeux à égalités comme objets botaniques

Troisième volet géométrique du chantier des jeux 2×2 — après GameTheory-03-Topology2x2-Python (la topologie des 576 jeux stricts et la grammaire des swaps) et GameTheory-03g-Deux-Especes-de-Fleches-Python (quand un swap préserve-t-il les équilibres), ce notebook décrit l’espace lui-même dans lequel ces transformations se déplacent.

La thèse, en une phrase : un jeu à égalités (tie game) n’est pas un jeu « plus flou » ou « plus général » qu’un jeu strict — c’est un objet de dimension inférieure, un mur où des chambres se touchent.

La lecture géométrique vient de la formalisation de Bruns & Kimmich (rapportée, cf. §Sources) :

chambre  →  mur  →  chambre voisine

Un « objet botanique » — un jeu à égalités depuis lequel plusieurs prolongements sont possibles — n’est donc pas une définition assouplie : c’est habiter le bord. La différence entre les deux notions est la différence entre relâcher un axiome et se placer sur une strate de codimension 1.

Plan : §1 énumère les strates (les 75 ordres faibles par joueur, classés par codimension) ; §2 mesure l’incidence chambre-mur (chaque mur sépare exactement deux chambres) et le graphe des chambres ; §3 traverse un mur sur un cas complet, puis relit la grammaire des swaps de GT-21 en longueurs de Coxeter ; §4 implémente make tie / break tie, les deux opérations de Bruns-Kimmich.

Conventions reprises de GT-3/GT-21 : un jeu 2×2 ordinal = deux tables de paiements (Ligne, Colonne), chacune un 4-uplet \((r_{11}, r_{12}, r_{21}, r_{22})\) de valeurs ; jeu strict = chaque table est une permutation de \(\{1,2,3,4\}\).

0. Configuration

Notebook pur standard library (itertools, collections) — aucune dépendance. Comme GT-21, tout ce qui est affirmé ici est calculé dans ce notebook ; ce qui vient de la littérature sans dérivation locale est rapporté comme dette (§Sources).

# GameTheory 3b : chambres, murs, codimension -- pur stdlib
from itertools import product
from collections import Counter, deque, defaultdict

print("GameTheory 3b : chambres, murs, codimension")
print("Representation : jeu = (table_ligne, table_colonne), tables = ordres faibles canoniques")
GameTheory 3b : chambres, murs, codimension
Representation : jeu = (table_ligne, table_colonne), tables = ordres faibles canoniques

1. L’espace des paiements et ses strates

1.1 Ordres faibles : la clé canonique

L’espace des paiements d’un joueur est \(\mathbb{R}^4\) (une coordonnée par case de sa table 2×2). Ce qui compte pour un jeu ordinal n’est pas la valeur numérique d’une coordonnée mais son rang relativement aux trois autres. Un point de cet espace code donc un ordre faible (weak order) sur les 4 cases : soit un ordre total (les 4 valeurs distinctes — une chambre), soit un ordre avec des ex æquo (un mur).

La représentation canonique : un 4-uplet \(t\) où \(t_i = 1 + (\text{nombre de valeurs distinctes strictement inférieures à } t_i)\). Ainsi \((2,2,4,5)\), \((1,1,3,4)\) et \((7,7,8,9)\) codent le même ordre faible — deux cases liées en bas, deux cases liées au-dessus — et partagent la clé canonique \((1,1,2,3)\).

Le discret \(\{1,2,3,4\}\) de GT-3/GT-21 est le cas particulier sans ex æquo : les 24 permutations de \((1,2,3,4)\).

# === Section 1.1 : enumeration des ordres faibles sur 4 cases ===

def canonique(t):
    """Cle canonique d'un 4-uplet : relabelage croissant des valeurs distinctes."""
    vals = sorted(set(t))
    return tuple(vals.index(v) + 1 for v in t)

# Enumeration : tous les 4-uplets de {1..4} (256), projetes sur leur cle canonique
ordres_faibles = sorted({canonique(t) for t in product(range(1, 5), repeat=4)})
print("Ordres faibles sur 4 cases :", len(ordres_faibles))
print("  stricts (chambres potentielles) :", sum(1 for t in ordres_faibles if len(set(t)) == 4))
print("  avec ex aequo (murs potentiels)  :", sum(1 for t in ordres_faibles if len(set(t)) < 4))
Ordres faibles sur 4 cases : 75
  stricts (chambres potentielles) : 24
  avec ex aequo (murs potentiels)  : 51

Lecture de la sortie committée : 75 ordres faibles — c’est le nombre de Fubini \(a(4)\), le compte des partitions ordonnées d’un ensemble à 4 éléments. Ils se partagent en 24 ordres totaux (les permutations — les chambres au sens strict, inchangées depuis GT-3) et 51 ordres avec ex æquo (les murs, par joueur).

Ce simple compte situe le changement de regard : le discret de GT-3 (\(24^2 = 576\) jeux stricts) vivait dans un espace où les égalités étaient exclues par construction. En admettant les ex æquo dans la représentation, l’espace passe de \(576\) à \(75^2 = 5625\) jeux à rangs — et ces nouveaux points ne sont pas des « jeux imprécis » : ce sont des strates. Reste à leur donner une dimension.

1.2 Codimension : compter des égalités indépendantes

La codimension d’une strate est le nombre d’équations indépendantes qui la définissent — la dimension perdue. Pour un ordre faible, ce n’est pas le nombre de paires égales mais le complément du nombre de blocs :

\[\text{codim}(t) = 4 - \#\text{blocs}(t).\]

Un bloc de taille 3 (triple égalité) porte 3 paires égales mais seulement 2 équations indépendantes (\(x_a = x_b\) et \(x_b = x_c\) suffisent, la troisième s’en déduit). Confondre paires et équations est l’erreur classique — le code suivant les compte séparément pour montrer qu’elles divergent.

# === Section 1.2 : codimension vraie vs nombre de paires egales ===

def nb_blocs(t):
    return len(set(t))

def nb_paires_egales(t):
    return sum(m * (m - 1) // 2 for m in Counter(t).values())

def codim(t):
    return 4 - nb_blocs(t)

print("Type de blocs           | codim | paires | exemples")
print("-" * 64)
par_type = defaultdict(list)
for t in ordres_faibles:
    signature = tuple(sorted(Counter(t).values(), reverse=True))
    par_type[signature].append(t)
for sig in sorted(par_type, reverse=True):
    ex = par_type[sig]
    paires_moy = sum(nb_paires_egales(t) for t in ex) // len(ex)
    exemples = str(ex[0]) + (", " + str(ex[1]) if len(ex) > 1 else "")
    print(f"blocs {str(sig):10s} ({len(ex):2d} ordres) |   {4 - len(sig)}   |   {paires_moy}    | {exemples}")
print()
dist_codim = Counter(codim(t) for t in ordres_faibles)
print("Distribution par codimension (par joueur) :", dict(sorted(dist_codim.items())))
print("Verification de la somme : 24 + 36 + 14 + 1 =", sum(dist_codim.values()), "= 75 ordres faibles")
Type de blocs           | codim | paires | exemples
----------------------------------------------------------------
blocs (4,)       ( 1 ordres) |   3   |   6    | (1, 1, 1, 1)
blocs (3, 1)     ( 8 ordres) |   2   |   3    | (1, 1, 1, 2), (1, 1, 2, 1)
blocs (2, 2)     ( 6 ordres) |   2   |   2    | (1, 1, 2, 2), (1, 2, 1, 2)
blocs (2, 1, 1)  (36 ordres) |   1   |   1    | (1, 1, 2, 3), (1, 1, 3, 2)
blocs (1, 1, 1, 1) (24 ordres) |   0   |   0    | (1, 2, 3, 4), (1, 2, 4, 3)

Distribution par codimension (par joueur) : {0: 24, 1: 36, 2: 14, 3: 1}
Verification de la somme : 24 + 36 + 14 + 1 = 75 = 75 ordres faibles

Lecture de la sortie committée : la taxonomy par taille de blocs croise les deux mesures.

blocs codim paires égales compte
\((1,1,1,1)\) 0 0 24 — les chambres
\((2,1,1)\) 1 1 36 — les murs simples
\((2,2)\) 2 2 6
\((3,1)\) 2 3 8
\((4)\) 3 6 1

Les deux lignes en gras portent la leçon : le triple ex æquo (les 8 cas \((3,1)\)) diverge — 3 paires mais codimension 2 — et le quadruple ex æquo porte 6 paires pour codimension 3. La codimension est l’auche correcte : elle mesure la dimension perdue, pas le nombre de coïncidences.

L’image botanique se précise : les murs de codimension 1 sont des cloisons (36 par joueur), ceux de codimension 2 des arêtes où les cloisons se rencontrent (6+8 = 14), celui de codimension 3 un sommet (1). Un objet botanique n’est pas « un jeu imprécis » : c’est un point de l’espace des paiements situé sur cette architecture.

1.3 Les 5 625 jeux à rangs, par codimension

Un jeu complet = une paire d’ordres faibles (Ligne, Colonne). La codimension du jeu est la somme des deux : un mur de Ligne dans une chambre de Colonne est un mur de codim 1 ; deux murs simultanés donnent codim 2 ; etc.

# === Section 1.3 : distribution des jeux par codimension totale ===

jeux = [(r, c) for r in ordres_faibles for c in ordres_faibles]
dist_jeux = Counter(codim(r) + codim(c) for r, c in jeux)
print("Jeux a rangs (paires d'ordres faibles) :", len(jeux))
print()
print("codim |  jeux  | remarque")
print("-" * 56)
remarques = {0: "les 576 chambres strictes (univers GT-3 / GT-21)",
             1: "murs simples : un seul ex aequo, d'un seul cote",
             2: "murs doubles : deux ex aequo (eventuellement croises)",
             6: "les deux tables entierement plates (jeu unique)"}
for k in sorted(dist_jeux):
    print(f"  {k}   | {dist_jeux[k]:5d}  | {remarques.get(k, '')}")
print()
print("Total :", sum(dist_jeux.values()), "= 75 x 75 attendu")
print("Chambres strictes :", dist_jeux[0], "-- coherentes avec GT-3 (576) et GT-21")
Jeux a rangs (paires d'ordres faibles) : 5625

codim |  jeux  | remarque
--------------------------------------------------------
  0   |   576  | les 576 chambres strictes (univers GT-3 / GT-21)
  1   |  1728  | murs simples : un seul ex aequo, d'un seul cote
  2   |  1968  | murs doubles : deux ex aequo (eventuellement croises)
  3   |  1056  | 
  4   |   268  | 
  5   |    28  | 
  6   |     1  | les deux tables entierement plates (jeu unique)

Total : 5625 = 75 x 75 attendu
Chambres strictes : 576 -- coherentes avec GT-3 (576) et GT-21

Lecture de la sortie committée : l’univers des jeux à rangs compte 5 625 états. Les 576 chambres strictes — tout l’univers de GT-3 et de GT-21 — n’en occupent que 10,2 %. Les 1 728 jeux de codimension 1 sont les murs simples ; 1 968 de codim 2 ; le reste s’étage jusqu’au jeu totalement plat (codim 6), unique, où chaque joueur est indifférent entre les quatre cases.

Autrement dit : le discret dans lequel la série a travaillé jusqu’ici était le squelette ouvert d’un édifice dont les murs, arêtes et sommets — 89,8 % des états à rangs — n’avaient pas de nom. La suite leur donne un rôle structurel : ce sont eux qui raccordent les chambres.

2. L’incidence chambre-mur

2.1 Chaque mur simple sépare exactement deux chambres

Un mur de codimension 1 (un seul ex æquo, d’un seul côté) est une cloison : la briser dans un sens ou dans l’autre donne exactement deux chambres. Réciproquement, depuis une chambre, quels murs touche-t-on ? Pas tous : pour que deux cases puissent se rencontrer, il faut qu’aucune autre valeur ne soit strictement comprise entre les leurs — sinon le mouvement croiserait d’autres murs d’abord. Les facettes d’une chambre correspondent aux paires de valeurs adjacentes dans son ordre.

Le code suivant mesure l’incidence dans les deux directions.

# === Section 2.1 : incidence double-face chambre <-> mur ===

chambres = [t for t in ordres_faibles if codim(t) == 0]      # 24 permutations
murs_simples = [t for t in ordres_faibles if codim(t) == 1]  # 36

def briser_le_tie(t):
    """Les deux chambres strictes adjacentes au mur simple t (cote de t seul)."""
    c = Counter(t)
    v_tie = [k for k, m in c.items() if m == 2][0]
    i, j = [p for p in range(4) if t[p] == v_tie]           # les deux cases liees
    below = sorted([p for p in range(4) if t[p] < v_tie], key=lambda p: t[p])
    above = sorted([p for p in range(4) if t[p] > v_tie], key=lambda p: t[p])
    # chambre A : i passe devant j ; chambre B : j devant i -- au NIVEAU du tie
    def rangs(ordre_positions):
        rk = [0] * 4
        for k, p in enumerate(ordre_positions):
            rk[p] = k + 1
        return tuple(rk)
    A = rangs(below + [i, j] + above)
    B = rangs(below + [j, i] + above)
    return A, B

# mur -> chambres
incidences_mur = {t: briser_le_tie(t) for t in murs_simples}
toutes_distinctes = all(a != b for a, b in incidences_mur.values())
print("Murs simples par cote :", len(murs_simples))
print("Chaque mur touche exactement 2 chambres distinctes :", toutes_distinctes)

# chambre -> murs (facettes)
facettes = defaultdict(list)
for t, (a, b) in incidences_mur.items():
    facettes[a].append(t)
    facettes[b].append(t)
print("Facettes par chambre  :", dict(Counter(len(v) for v in facettes.values())),
      "(3 attendu : les 3 paires de valeurs adjacentes)")
Murs simples par cote : 36
Chaque mur touche exactement 2 chambres distinctes : True
Facettes par chambre  : {3: 24} (3 attendu : les 3 paires de valeurs adjacentes)

Lecture de la sortie committée : la double incidence est exacte.

  • Mur → chambres : chacun des 36 murs simples d’un côté touche exactement 2 chambres distinctes — les deux résolutions de son ex æquo. Une cloison n’a jamais trois pièces de chaque côté.
  • Chambre → murs : chacune des 24 chambres a exactement 3 facettes — les 3 paires de valeurs adjacentes de son ordre. Une paire non adjacente (les valeurs 1 et 3, avec 2 entre elles) ne définit pas une facette : pour amener 1 et 3 à l’égalité, il faut d’abord traverser le mur où 1 rencontre 2.

C’est la structure d’arrangement d’hyperplans de l’espace des paiements (les murs étant les hyperplans \(x_i = x_j\)) — calculée ici sans en invoquer la théorie : les comptes parlent d’eux-mêmes. Au niveau du jeu complet (deux côtés), chaque chambre a donc \(3 + 3 = 6\) facettes, et le nombre de murs simples du jeu vaut \(2 \times 36 \times 24 = 1728\).

Remarque de parité avec GT-21 : le théorème « le mur ne compte que s’il est habité » de GT-21 portait sur les meilleures réponses (quelles stratégies habitent la colonne traversée) ; ici l’habitation est géométrique — quelles chambres touchent le mur. Les deux lectures se complètent : GT-21 dit quand un swap change les équilibres, GT-3b dit où le swap se déplace.

2.2 Le graphe des chambres : connexité et diamètre

Déclarer que les murs « raccordent » les chambres donne un graphe : les sommets sont les 576 chambres, une arête par mur simple traversé. La bonne nouvelle pour la grammaire des swaps : ce graphe est connexe — n’importe quel jeu strict est atteignable depuis n’importe quel autre en traversant des murs un à un.

# === Section 2.2 : BFS sur le graphe des chambres ===

def swap_valeurs_adjacentes(t, k):
    """Echange les cases portant les valeurs k et k+1 (traversee de la facette k<->k+1)."""
    pk, pk1 = t.index(k), t.index(k + 1)
    l = list(t)
    l[pk], l[pk1] = l[pk1], l[pk]
    return tuple(l)

adjacences = {t: [swap_valeurs_adjacentes(t, k) for k in (1, 2, 3)] for t in chambres}

# BFS cote 1 joueur, depuis (1,2,3,4)
depart = (1, 2, 3, 4)
dist = {depart: 0}
q = deque([depart])
while q:
    u = q.popleft()
    for v in adjacences[u]:
        if v not in dist:
            dist[v] = dist[u] + 1
            q.append(v)
print("Cote 1 joueur : connexe =", len(dist) == 24, "| diametre =", max(dist.values()))
print("  repartition des distances :", dict(sorted(Counter(dist.values()).items())))

# BFS jeu complet : (row, col), 576 sommets
dep2 = ((1, 2, 3, 4), (1, 2, 3, 4))
dist2 = {dep2: 0}
q2 = deque([dep2])
while q2:
    (ur, uc) = q2.popleft()
    d_cur = dist2[(ur, uc)]
    voisins_row = [(swap_valeurs_adjacentes(ur, k), uc) for k in (1, 2, 3)]
    voisins_col = [(ur, swap_valeurs_adjacentes(uc, k)) for k in (1, 2, 3)]
    for v in voisins_row + voisins_col:
        if v not in dist2:
            dist2[v] = d_cur + 1
            q2.append(v)
print()
print("Jeu complet : connexe =", len(dist2) == 576, "| diametre =", max(dist2.values()))
print("  repartition :", dict(sorted(Counter(dist2.values()).items())))
print("  aretes = 576 x 6 / 2 =", 576 * 6 // 2, "-- a comparer aux 1728 murs simples du 1.3")
Cote 1 joueur : connexe = True | diametre = 6
  repartition des distances : {0: 1, 1: 3, 2: 5, 3: 6, 4: 5, 5: 3, 6: 1}

Jeu complet : connexe = True | diametre = 12
  repartition : {0: 1, 1: 6, 2: 19, 3: 42, 4: 71, 5: 96, 6: 106, 7: 96, 8: 71, 9: 42, 10: 19, 11: 6, 12: 1}
  aretes = 576 x 6 / 2 = 1728 -- a comparer aux 1728 murs simples du 1.3

Lecture de la sortie committée : le graphe des chambres est connexe, de diamètre 6 par côté et 12 pour le jeu complet — le pire cas étant l’ordre exactement renversé des deux tables (\(6 + 6\) inversions).

Deux régularités lisibles dans la sortie :

  • la distribution des distances d’un côté, \(\{0{:}1, 1{:}3, 2{:}5, 3{:}6, 4{:}5, 5{:}3, 6{:}1\}\), est symétrique : le mot le plus long (l’ordre renversé) est unique, comme le plus court (l’identité) ;
  • au niveau du jeu, chaque chambre a 6 voisins (3 facettes par côté), et le compte d’arêtes \(576 \times 6 / 2 = 1728\) coïncide exactement avec le nombre de murs simples comptés au §1.3 — chaque mur simple est une arête du graphe, chaque arête est la traversée d’un mur. Le pont entre le comptage de strates (§1) et la connectivité (§2) est un double-comptage propre.

Conséquence pour la série : le discret \(\{1,2,3,4\}\) de GT-3 n’était pas un artefact de commodité — c’est un transversal des chambres (un point par chambre), et la connexité ci-dessus garantit que la grammaire des swaps adjacents suffit à relier tout l’univers strict.

3. Traverser un mur : chambre → mur → chambre

3.1 Un cas complet, matrices à l’appui

Prenons le Dilemme du Prisonnier dans l’encodage ordinal de GT-21 — Ligne \((3,1,4,2)\), Colonne \((3,4,1,2)\) — et suivons le swap de valeurs adjacentes \(R(3,4)\) (Ligne échange les cases portant 3 et 4). Ce swap traverse la facette où les deux cases porteuses deviennent égales : le mur \(r_{11} = r_{21}\).

# === Section 3.1 : traverser le mur r11=r21 depuis le Dilemme du Prisonnier ===

def afficher_jeu(nom, row, col):
    print(nom)
    print(f"  Ligne    | {row[0]:>2}  {row[1]:<2}|   Colonne | {col[0]:>2}  {col[1]:<2}|")
    print(f"           | {row[2]:>2}  {row[3]:<2}|           | {col[2]:>2}  {col[3]:<2}|")
    print()

def faire_le_tie(t, k):
    """Coalescence des valeurs k et k+1 -> mur canonique (make_tie du 4.1, cote seul)."""
    return canonique(tuple(k if v == k + 1 else v for v in t))

# La chambre de depart : le PD (encodage GT-21)
pd_row, pd_col = (3, 1, 4, 2), (3, 4, 1, 2)
afficher_jeu("CHAMBRE A -- Dilemme du Prisonnier (strict)", pd_row, pd_col)

# Le mur : coalescence des valeurs 3 et 4 de Ligne (cases r11 et r21)
mur_row = faire_le_tie(pd_row, 3)
afficher_jeu("MUR (codim 1) -- Ligne indifferent r11 = r21", mur_row, pd_col)

# La chambre d'arrivee : resolution du tie dans l'autre sens
chambre_B_row = swap_valeurs_adjacentes(pd_row, 3)
afficher_jeu("CHAMBRE B -- apres traversee (swap R(3,4))", chambre_B_row, pd_col)

print("Verification : mur =", mur_row)
print("  briser_le_tie(mur) redonne les deux faces :", briser_le_tie(mur_row))
print("  chambre A :", canonique(pd_row), " chambre B :", canonique(chambre_B_row))
CHAMBRE A -- Dilemme du Prisonnier (strict)
  Ligne    |  3  1 |   Colonne |  3  4 |
           |  4  2 |           |  1  2 |

MUR (codim 1) -- Ligne indifferent r11 = r21
  Ligne    |  3  1 |   Colonne |  3  4 |
           |  3  2 |           |  1  2 |

CHAMBRE B -- apres traversee (swap R(3,4))
  Ligne    |  4  1 |   Colonne |  3  4 |
           |  3  2 |           |  1  2 |

Verification : mur = (3, 1, 3, 2)
  briser_le_tie(mur) redonne les deux faces : ((3, 1, 4, 2), (4, 1, 3, 2))
  chambre A : (3, 1, 4, 2)  chambre B : (4, 1, 3, 2)

Lecture de la sortie committée : le triplet complet chambre A → mur → chambre B est exhibé avec les trois matrices — l’acceptation de l’exercice 2 est satisfaite et démontrée :

  • chambre A : le PD strict, où \(r_{21} = 4 > r_{11} = 3\) (Ligne gagne plus à dévier) ;
  • mur : le jeu à égalité où \(r_{11} = r_{21}\) — Ligne est exactement indifférent entre ses deux stratégies quand Colonne joue à gauche ; c’est le point de bascule où la défection cesse d’être strictement meilleure en première colonne ;
  • chambre B : l’ordre renversé de la paire \((3,4)\) — le swap \(R(3,4)\) de GT-21.

La dernière ligne vérifie mécaniquement la cohérence : le briser_le_tie du §2.1 appliqué au mur redonne bien les deux chambres — A et B — comme les deux faces de la cloison. Le swap de GT-21, redéfini géométriquement, est la traversée de ce mur : rien d’autre.

Fenêtre pédagogique : sur le mur, l’ensemble des meilleures réponses de Ligne en première colonne grossit (de 1 à 2 choix), puis redevient singulier de l’autre côté — avec l’autre choix. Un mur est exactement le lieu où la structure des meilleures réponses change de cardinal — la version locale du « mur habité » de GT-21.

3.2 La grammaire des swaps relue en longueurs de Coxeter

GT-21 étudiait six swaps par côté : \(R(a,b)\) pour toutes les paires de valeurs. La géométrie les sépare en deux familles :

  • \(|a-b| = 1\) (valeurs adjacentes) : le swap traverse un seul mur — une facette ;
  • \(|a-b| \geq 2\) : les valeurs sont séparées par d’autres — le swap est un raccourci qui enjambe plusieurs murs.

La longueur exacte (nombre de traversées de facettes) d’une transposition de valeurs à distance \(d\) est \(2d-1\).

# === Section 3.2 : les swaps GT-21 en longueurs de Coxeter ===

print("Swap  |  traversees de murs  |  statut")
print("-" * 50)
for (a, b) in [(1, 2), (2, 3), (3, 4), (1, 3), (2, 4), (1, 4)]:
    d = abs(a - b)
    statut = "facette (1 mur)" if d == 1 else f"raccourci ({2*d-1} murs enjambes)"
    print(f"R({a},{b})  |         {2*d-1}            |  {statut}")

# Verification : R(1,3) = R(1,2) puis R(2,3) puis R(1,2)
def appliquer_swap(t, a, b):
    return tuple(b if v == a else (a if v == b else v) for v in t)

t0 = (1, 2, 3, 4)
direct = appliquer_swap(t0, 1, 3)
compose = appliquer_swap(appliquer_swap(appliquer_swap(t0, 1, 2), 2, 3), 1, 2)
print()
print("R(1,3) applique a (1,2,3,4)      :", direct)
print("R(1,2) puis R(2,3) puis R(1,2)   :", compose)
print("Egalite (le raccourci = 3 facettes) :", direct == compose)
Swap  |  traversees de murs  |  statut
--------------------------------------------------
R(1,2)  |         1            |  facette (1 mur)
R(2,3)  |         1            |  facette (1 mur)
R(3,4)  |         1            |  facette (1 mur)
R(1,3)  |         3            |  raccourci (3 murs enjambes)
R(2,4)  |         3            |  raccourci (3 murs enjambes)
R(1,4)  |         5            |  raccourci (5 murs enjambes)

R(1,3) applique a (1,2,3,4)      : (3, 2, 1, 4)
R(1,2) puis R(2,3) puis R(1,2)   : (3, 2, 1, 4)
Egalite (le raccourci = 3 facettes) : True

Lecture de la sortie committée : la grammaire de GT-21 se recompose géométriquement.

  • les trois swaps adjacents \(R(1,2)\), \(R(2,3)\), \(R(3,4)\) sont les générateurs : chacun traverse exactement un mur (une facette). Avec leurs homologues Colonne, ils engendrent tout le graphe des 576 chambres (§2.2) ;
  • les trois raccourcis \(R(1,3)\), \(R(2,4)\), \(R(1,4)\) enjambent respectivement 3, 3 et 5 murs. La décomposition est vérifiée dans la sortie : \(R(1,3) = R(1,2) \circ R(2,3) \circ R(1,2)\) — le raccourci et la cascade de facettes aboutissent au même 4-uplet, mais la cascade rencontre les murs intermédiaires (des jeux à égalités où la structure des meilleures réponses peut basculer), quand le raccourci les saute.

C’est la réponse géométrique à une question laissée ouverte par GT-21 : pourquoi certains swaps « ne comptent-ils » pas (préservation des équilibres) ? Parce qu’un swap n’agit sur les équilibres qu’à travers les murs qu’il traverse qui sont effectivement habités (au sens meilleures réponses de GT-21). Les facettes rencontrées par la cascade sont autant d’occasions de bascule ; le raccourci n’échantillonne que l’état final.

4. Make tie / break tie : les deux opérations de Bruns-Kimmich

4.1 Les opérations, implémentées

La description géométrique s’accompagne de deux opérations duuales (rapportées de Bruns-Kimmich, implémentées ici) :

  • make tie : depuis une chambre, rapprocher deux valeurs adjacentes jusqu’à l’égalité — on pose le jeu sur un mur (codim +1) ;
  • break tie : depuis un mur, séparer l’ex æquo dans un sens ou dans l’autre — on retombe dans l’une des deux chambres adjacentes (codim −1).

Ce sont exactement les briques des §2-3, promues au rang d’API.

# === Section 4.1 : make_tie / break_tie, operations duales ===

def make_tie(jeu, cote, k):
    """Coalesce les valeurs k et k+1 du cote donne -> jeu sur un mur (codim +1)."""
    row, col = jeu
    t = faire_le_tie(row, k) if cote == "ligne" else faire_le_tie(col, k)
    return (t, col) if cote == "ligne" else (row, t)

def break_tie(jeu, cote, sens):
    """Separe le premier ex aequo du cote donne, ordre 'haut' ou 'bas' -> chambre (codim -1)."""
    row, col = jeu
    t = row if cote == "ligne" else col
    c = Counter(t)
    v = [k for k, m in c.items() if m == 2]
    if not v:
        return jeu  # pas d'ex aequo : operation identite
    i, j = sorted(p for p in range(4) if t[p] == v[0])
    l = list(t)
    l[i], l[j] = (v[0], v[0] + 1) if sens == "haut" else (v[0] + 1, v[0])
    t2 = canonique(l)
    return (t2, col) if cote == "ligne" else (row, t2)

# Demonstration : aller-retour complet depuis le PD
pd = ((3, 1, 4, 2), (3, 4, 1, 2))
mur = make_tie(pd, "ligne", 3)
retour_haut = break_tie(mur, "ligne", "haut")
retour_bas = break_tie(mur, "ligne", "bas")
print("Chambre (PD)        :", pd)
print("make_tie(ligne, 3)  :", mur, " (codim", codim(mur[0]) + codim(mur[1]), ")")
print("break_tie(haut)     :", retour_haut, "-> retrouve le PD :", retour_haut == pd)
print("break_tie(bas)      :", retour_bas, "-> l'autre chambre (chambre B du 3.1)")
print()
print("Le mur porte bien 2 sorties distinctes :", retour_haut != retour_bas)
Chambre (PD)        : ((3, 1, 4, 2), (3, 4, 1, 2))
make_tie(ligne, 3)  : ((3, 1, 3, 2), (3, 4, 1, 2))  (codim 1 )
break_tie(haut)     : ((3, 1, 4, 2), (3, 4, 1, 2)) -> retrouve le PD : True
break_tie(bas)      : ((4, 1, 3, 2), (3, 4, 1, 2)) -> l'autre chambre (chambre B du 3.1)

Le mur porte bien 2 sorties distinctes : True

Lecture de la sortie committée : l’aller-retour est mécaniquement vérifié. make_tie fait perdre une dimension (le jeu quitte l’ouvert des chambres pour la cloison), break_tie la rend — dans un des deux sens : la sortie haut retrouve le PD d’origine, la sortie bas atterrit dans la chambre B du §3.1. Les deux sorties sont distinctes : c’est la matérialisation calculée du « plusieurs prolongements sont possibles » de la thèse botanique.

Un jeu à égalités n’est donc pas une spécification incomplète d’un jeu strict : c’est un objet dont le type même est différent — un point de l’architecture, avec ses deux faces — et dont le prolongement exige un choix supplémentaire (un sens). Toute la différence entre assouplir une définition et habiter le bord.

4.2 Trois archétypes au bout des murs

Bruns-Kimmich (rapporté) décrivent les jeux 2×2 primitifs comme engendrés depuis des murs par make/break tie, sous trois archétypes : indépendance, coordination, échange. Nos implémentations permettent d’en donner des instanciations calculées — la définition exacte des archétypes dans la source relève de la dette de sources (§Sources) ; ce qui suit instancie chaque nom par un mur de géométrie naturelle, obtenu par make_tie et vérifié par break_tie.

# === Section 4.2 : trois instanciations (RAPPORTEES, cf. Sources) ===

print("Archetype       | mur obtenu (Ligne | Colonne)            | geometrie du tie")
print("-" * 90)

# Independance : mur vertical Ligne (r11 = r21) -- Ligne indifferent entre ses
# deux strategies quand Colonne joue a gauche
base_ind = ((1, 3, 2, 4), (1, 3, 2, 4))
mur_ind = make_tie(base_ind, "ligne", 1)
print(f"Independance    | {str(mur_ind[0]):18s} | {str(mur_ind[1]):18s} | vertical (r11 = r21)")

# Coordination : double mur anti-diagonal (r12 = r21 chez les deux joueurs)
base_co = ((1, 2, 3, 4), (1, 2, 3, 4))
mur_co = make_tie(make_tie(base_co, "ligne", 2), "colonne", 2)
print(f"Coordination    | {str(mur_co[0]):18s} | {str(mur_co[1]):18s} | anti-diagonal x2, codim {codim(mur_co[0]) + codim(mur_co[1])}")

# Echange : mur horizontal Ligne (r11 = r12) -- le gain de Ligne en haut ne
# depend pas de la colonne de Colonne
base_ech = ((1, 2, 3, 4), (2, 1, 4, 3))
mur_ech = make_tie(base_ech, "ligne", 1)
print(f"Echange         | {str(mur_ech[0]):18s} | {str(mur_ech[1]):18s} | horizontal (r11 = r12)")

print()
print("Chaque mur se brise dans 2 sens -> chaque archetype borde 2 familles de jeux stricts.")
print("(Instanciations de notre cru ; la definition source des archetypes : dette, cf. Sources.)")
Archetype       | mur obtenu (Ligne | Colonne)            | geometrie du tie
------------------------------------------------------------------------------------------
Independance    | (1, 2, 1, 3)       | (1, 3, 2, 4)       | vertical (r11 = r21)
Coordination    | (1, 2, 2, 3)       | (1, 2, 2, 3)       | anti-diagonal x2, codim 2
Echange         | (1, 1, 2, 3)       | (2, 1, 4, 3)       | horizontal (r11 = r12)

Chaque mur se brise dans 2 sens -> chaque archetype borde 2 familles de jeux stricts.
(Instanciations de notre cru ; la definition source des archetypes : dette, cf. Sources.)

Lecture de la sortie committée : chaque archétype est matérialisé par un jeu mur obtenu par calcul depuis un jeu strict :

  • indépendance — mur vertical de Ligne (\(r_{11} = r_{21}\)) : Ligne exactement indifférent entre ses deux stratégies face à la colonne gauche ; brisé vers le haut ou le bas, ce mur fabrique les jeux où Ligne a une préférence stricte — c’est le voisinage des décisions unilatérales ;
  • coordination — double mur anti-diagonal (codim 2, une arête de l’architecture) : les deux joueurs lisent leur duel sur la même paire de cases ; brisé dans le même sens des deux côtés → coordination pure, en sens opposés → conflit — le voisinage des jeux de rencontre ;
  • échange — mur horizontal de Ligne (\(r_{11} = r_{12}\)) : le gain de Ligne en haut ne dépend pas de l’action de Colonne ; brisé, il fabrique les jeux où l’issue dépend du croisement des deux décisions — le voisinage des marchandages.

Ces instanciations calculées bornent honnêtement ce que ce notebook établit : les opérations, les comptes, les incidences. La généalogie complète des 2×2 par archétypes — les trois primitives de Bruns-Kimmich — est rapportée, non dérivée : elle exige la source primaire (§Sources, dette n°4).

Exercices

Exercice 1 — Énumérer les murs par codimension

Reproduisez et étendez le comptage du §1 : énumérez les 75 ordres faibles, comptez-les par codimension, puis par type de blocs (tailles décroissantes). Vérifiez que la somme des comptes par type redonne 75, et que chaque type de mur \((2,1,1)\), \((2,2)\), \((3,1)\), \((4)\) est non vide.

Indice : la signature de blocs s’obtient avec Counter ; la codimension est 4 - len(set(t)). Pour le compte par type, regroupez par tuple(sorted(Counter(t).values(), reverse=True)).

Étape 1 : énumérer les 75 clés canoniques (§1.1). Étape 2 : grouper par signature. Étape 3 : afficher le compte par signature et vérifier la somme.

# Exercice 1 : enumeration des murs par codimension et par type de blocs
# TODO etudiant : reproduire la table du 1.2 (75 ordres faibles, 4 signatures de murs)
# Indice : signature = tuple(sorted(Counter(t).values(), reverse=True))
resultat_ex1 = {}  # TODO etudiant : {signature: nombre d'ordres faibles}
print("Exercice 1 : a completer (compte par type de blocs)")
Exercice 1 : a completer (compte par type de blocs)

Exercice 2 — Traverser : exhiber un triplet complet

Choisissez un jeu strict qui n’est pas le PD (par exemple le jeu de la poule mouillée — vérifiez son encodage ordinal avant de l’utiliser), une de ses facettes, et exhibez le triplet (chambre A, mur, chambre B) complet avec les trois matrices, comme au §3.1.

Indice : une facette = une paire de valeurs adjacentes \((k, k+1)\) ; le mur s’obtient par faire_le_tie(table, k) ; la chambre B par swap_valeurs_adjacentes(table, k) ; vérifiez enfin que briser_le_tie(mur) contient bien les deux chambres.

Étape 1 : poser les tables du jeu choisi. Étape 2 : choisir \(k\) et construire le mur et la chambre B. Étape 3 : vérifier la double incidence avec briser_le_tie.

# Exercice 2 : exhiber chambre -> mur -> chambre pour un autre jeu
# TODO etudiant : remplir les trois matrices et la verification
chambre_A = None  # TODO etudiant : (row, col) du jeu strict choisi
mur_ex2 = None    # TODO etudiant : le mur traverse (via faire_le_tie)
chambre_B = None  # TODO etudiant : la chambre d'arrivee (via swap_valeurs_adjacentes)
print("Exercice 2 : a completer (triplet chambre-mur-chambre)")
Exercice 2 : a completer (triplet chambre-mur-chambre)

Exercice 3 — Make tie / break tie : vers les archétypes

Depuis le jeu Stag Hunt de GT-21 (Ligne \((4,1,3,2)\), Colonne \((4,3,1,2)\)), construisez par make_tie un mur de chaque type : un mur Ligne, un mur Colonne, puis un double mur (codim 2). Pour chacun, appliquez break_tie dans les deux sens et observez les deux chambres de sortie — vous venez de cartographier le voisinage d’un jeu de coordination.

Indice : le double mur s’obtient en composant deux make_tie successifs (comme au §4.2 pour la coordination). Les deux sens de break_tie doivent redonner deux jeux stricts distincts.

Étape 1 : construire les trois murs. Étape 2 : pour chacun, briser dans les deux sens. Étape 3 : vérifier que chaque paire de sorties est distincte et stricte.

# Exercice 3 : voisinage de Stag Hunt par make_tie / break_tie
# TODO etudiant : les trois murs (ligne, colonne, double) et leurs sorties
murs_ex3 = None     # TODO etudiant : {nom_du_mur: jeu_mur}
sorties_ex3 = None  # TODO etudiant : {nom_du_mur: (sortie_haut, sortie_bas)}
print("Exercice 3 : a completer (voisinage de Stag Hunt)")
Exercice 3 : a completer (voisinage de Stag Hunt)

Conclusion

Ce que ce notebook établit (tout calculé, tout vérifié dans les sorties) :

  1. l’univers des jeux 2×2 à rangs compte \(75^2 = 5625\) états, dont les 576 jeux stricts ne sont que les chambres ouvertes (10,2 %) — le reste est l’architecture : 1728 murs simples, 1968 strates de codim 2, jusqu’au jeu totalement plat (codim 6, unique) ;
  2. l’incidence est exacte : chaque mur simple sépare exactement 2 chambres, chaque chambre a exactement 3 facettes par côté — et les 1728 arêtes du graphe des chambres coïncident mur pour mur avec les 1728 strates de codim 1 ;
  3. le graphe des chambres est connexe, de diamètre 6 par côté (12 pour le jeu) — avec la distribution symétrique des distances \(\{1,3,5,6,5,3,1\}\) ;
  4. la grammaire des swaps de GT-21 se recompose : les 3 swaps adjacents sont des traversées de facettes (1 mur), les 3 autres des raccourcis (3, 3 et 5 murs enjambés), avec \(R(1,3) = R(1,2) \circ R(2,3) \circ R(1,2)\) vérifié ;
  5. make tie / break tie sont implémentées et l’aller-retour est vérifié : un jeu à égalités est un objet biface, pas une définition relâchée.

Ce qui reste ouvert — les quatre dettes de dérivation, à honorer depuis les sources primaires avant toute affirmation publique : le quotient \(576 \to 144\) (facteur exact à re-dériver), l’action du groupe \(S_4 \times S_4\), le tore à 37 trous, et la vérification firsthand des trois arXiv. Ce notebook ne les affirme pas : il les porte comme dette.

La phrase à retenir : un objet botanique n’est pas un objet plus flou — c’est un objet de dimension inférieure, depuis lequel plusieurs prolongements sont possibles.

Sources et dettes

  • Bruns & Kimmich — la description géométrique (chambres/murs/strates, make/break tie, archétypes) est rapportée de leur formalisation des jeux 2×2, non dérivée de la source primaire ici (dette n°4). Tout ce que le notebook affirme chiffré (75, 5625, 1728, 3, 2, 6, 12) est calculé localement et reproductible en ré-exécutant.
  • Robinson & Goforth — le tableau périodique des jeux 2×2 ; ancêtre Rapoport & Guyer. (L’attribution « Gale et Morris » qui circule dans certains transcripts est une hallucination corrigée.)
  • Dettes ouvertes (HARD) : (1) quotient 576→144 à re-dériver ; (2) groupe \(S_4 \times S_4\) et son action ; (3) tore à 37 trous ; (4) arXiv 2309.15981, 2102.00053, 1704.02230 — identités vérifiées firsthand (passe #11168 du 2026-08-31 : Tohmé & Viglizzo 2023 · Czechowski & Piliouras 2021 · Hedges 2017) ; lecture intégrale ouverte.
  • Vocabulaire : volontairement minimal — chambres, murs, strates, codimension, facettes. Aucun vocabulaire faisceautique au-delà de ce que le notebook calcule.

Chantier des jeux 2×2 — voir aussi : GT-3 Topology2x2 (l’espace et la grammaire) · GT-3h Deux-Espèces-de-Flèches (quand un swap est un morphisme) · GT-3c « chemin minimal de swaps » (issue #12222, à venir).

Retour au sommet