ogdoad 1.0.0

Clifford algebras (with nilpotents) over the field-like subclasses of combinatorial games: nimbers, surreals, surcomplex.
Documentation
# AGENTS.md — `src/games/`

The PILLAR of combinatorial game theory — the second column of the project,
mostly independent of the scalar/Clifford stack (the bridge is the number/nimber
subclasses, where Conway multiplication is defined). Games under disjunctive sum
are an abelian GROUP, not a ring; that constraint is *why* the Clifford story
lives on the scalar backends and not on all games.

> Read `docs/OPEN.md` before touching `coin_turning.rs`, `kernel.rs`, `misere.rs`, or
> the example probes — they feed the open play-semantics question.

`mod.rs` re-exports every module below flat.

Fixed-width game payloads use `u128`/`i128`: finite nim heaps, octal codes, Grundy
values, scoring integers, and quotient counters. `usize` is for graph nodes, option
indices, and collection lengths.

## Values & arithmetic

- **`partizan.rs`** — short partizan games (sum/neg/order/birthday/is_number) + the
  CANONICAL FORM (dominated/reversible reduction; `structural_string` vs
  `canonical_string` — the latter canonicalizes, a value key) + the game↔surreal
  bridge (`number_value`/`from_surreal`, numbers only). Also `Game::ordinal_sum`
  (G:H — Hackenbush strings are these), `Game::nim_heap` (⋆n), `Game::is_all_small`.
  The integer-value-of-a-game logic lives once in `partizan::integer_value` (callers
  route through it, no duplicate inline copies).
- **`number_game.rs`** — transfinite NUMBER games (ω, ε) carried by their Surreal
  value — value/birthday/sum/cmp delegate to surreal, no infinite option tree. Plus
  the FULL transfinite round trip via sign_expansion/from_sign_expansion (the
  run-length sign expansion is the finite encoding of the infinite {L|R} tree).
- **`nimber_game.rs`** — the CHAR-2 MIRROR of `number_game.rs`: transfinite NIMBER
  games (Nim heaps `⋆α`) carried by their `Ordinal` (On₂) Grundy value — grundy/
  add(=nim-add XOR)/cmp/to_finite_game delegate to `Ordinal`, no infinite option
  tree. `neg` is the identity (char 2: every impartial game is self-inverse);
  `turning_corners` is the nim-product (Conway's coin game, `ω³=2`); `None` only when
  a Kummer carry needs a prime past the verified table or at `≥ ω^(ω^ω)`. This is the
  `No ↔ On₂` symmetry at the games layer (the rest lives at the scalar layer via the
  shared CNF core, reaching Clifford through `Scalar for Ordinal` inside the checked
  Kummer boundary). Bound to Python as `NimberGame`.
- **`game_exterior/`** — the exterior algebra of the GAME group: Λ over ℤ on game
  generators (living on all of game-world, incl. non-numbers ⋆/↑ — needs only the
  ℤ-module structure). Split into three layers:
  - `game_exterior/relations.rs``GameRelation`, `GameRelationCertificate`,
    `RelationSearchCertificate` + certificate helpers (`pub(super)` to `lambda.rs`
    and `clifford.rs`).
  - `game_exterior/lambda.rs``GameExterior` (free Grassmann engine quotiented by
    integer game relations such as `2⋆=0`) + all private helpers; lattice
    normalization in `linalg/integer.rs`.
  - `game_exterior/clifford.rs``GameCliffordError` and `GameClifford`: the checked
    integer-valued deformation surface; hand-supplied `q`/polar tables are accepted
    only when every game relation is null and polar-radical, so torsion-free targets
    force the documented vanishings (for example, `2⋆=0` kills `Q(⋆)` and all
    pairings with ⋆). This is not a game-native quadratic-data theorem; that remains
    in `docs/OPEN.md`.
  - `game_exterior/mod.rs` — hub; re-exports everything flat so `games::GameExterior`
    etc. remain unchanged.

## Temperature theory

- **`thermography.rs`** — the thermograph of a short game: left/right scaffolds,
  stops, cooling (`cooled_stops`), temperature, and mean (mast) value.
- **`heating.rs`** — game-valued heating, Berlekamp overheating `int_s^t G`, and
  Norton multiplication `G.U` by a positive unit. Infrastructure only: it does not
  assert the associated-graded product asked for in `docs/OPEN.md` `under`.
- **`atomic_weight.rs`** — atomic weight of ALL-SMALL games (finishes thermography):
  the two-ahead rule (Siegel Constructive Atomic Weight; Larsson–Nowakowski
  arXiv:2007.03949 Thm 10). `aw` IS additive on all-small games.
- **`piecewise.rs`**`Pl`: exact rational piecewise-linear wall arithmetic used by
  thermography. `add_pl` (pointwise sum) is the tropical ``; `sub_pl` is the arithmetic
  difference (`left_raw − right_raw`) in the meeting-temperature recursion, NOT a
  tropical operation.
- **`tropical_thermography.rs`** — names the latent tropical structure in
  thermography and machine-checks it. The option folds are tropical `` in DUAL
  semirings — the left wall a `(max,+)` fold over the Left options' right walls, the
  right wall a `(min,+)` fold over the Right options' left walls — and cooling is
  tropical ``. `Pl::oplus_max`/`oplus_min`/`otimes` name the wall operations;
  `thermograph_via_tropical` is a parallel recursion pinned EQUAL to
  `thermography::thermograph`. It reuses the identical `pub(crate)`
  freeze/meeting-temperature cooling tail — it only renames the folds, it does not
  reimplement cooling. The `Semiring`/`Tropical<C>` algebra it points at lives in
  `scalar/tropical.rs`.

## Impartial / outcome analysis

- **`coin_turning.rs`**`nim_mul_mex`: nim-mult as Conway's Turning-Corners mex
  recurrence (a different *definition* from the algebraic `nim_mul`, proven equal).
  Plus general 1-D coin-turning (`grundy_1d`) and the 2-D Tartan product
  (`tartan_grundy`), with the Tartan/Product theorem verified.
- **`grundy.rs`** — general Sprague–Grundy (normal-play impartial center): `mex`
  (the crate's one minimal-excludant — `lexicode.rs` and every other caller route
  through `grundy::mex`, no re-implementations),
  `grundy_graph` (DAG; None on a cycle), closure-based `grundy`. P-position ⟺ g=0;
  SG theorem `g(G+H)=g(G)⊕g(H)` pinned vs Bouton.
- **`kernel.rs`** — normal-play Win/Loss/Draw outcomes of any finite game graph
  (retrograde analysis); `p_positions` = Loss. The interactive route to the open
  question. Plus `scoring_values`: the Milnor minimax `ScoreInterval { left, right }`
  (`i128`) on a DAG — the integer-valued scoring knob.
- **`loopy/`** — loopy (cyclic) games, the third escape from XOR-linear P-sets: a
  cyclic rule admits a **Draw** outcome (a genuinely new degree of freedom). Split
  into five layers:
  - `loopy/catalogue.rs``LoopyWinner`, `LoopyPartizanOutcome`, `PartizanOutcome`,
    and the `LoopyValue` catalogue (`Zero`/`Star`/`On`/`Off`/`Over`/`Under`/
    `PlusMinus`/`Tis`/`Tisn`/`Dud` plus integer `s&t` tags, with exact
    starter-pair `outcome`, `partizan_outcome`, `sides`, neg/partial order/partial
    sum).
  - `loopy/graph.rs``LoopyGraph` (a thin computable wrapper over
    `kernel::outcomes` — loss/win/draw sets).
  - `loopy/partizan.rs``LoopyPartizanGraph`: validated finite two-sided
    Left/Right graphs; graph negation, budgeted reachable product sums and finite
    `Game` embedding; turn-expanded stopper detection with cycle witnesses;
    retrograde analysis returning exact starter pairs via `LoopyPartizanOutcome`
    and only projecting to `PartizanOutcome {P,N,L,R,Draw}` when that projection
    is honest.
  - `loopy/nim_values.rs``LoopyNimber`, `LoopyNimCertificate`,
    `loopy_nim_values`/`loopy_nim_values_certified`: Draw ⇒ `Side`/∞, else a
    nimber; exact on an acyclic non-Draw subgraph; bounded sidling only when the
    mex fixed point is unique; additive finite-nimber claims require the checked
    `recovery_condition_holds` flag.
  - `loopy/research.rs``loopy_decision_sets`/`loopy_quadric_probe`: read a
    cyclic rule's Loss-set AND Draw-set, each fit by `fit_f2_quadratic`.
  - `loopy/mod.rs` — hub; re-exports everything flat so `games::LoopyValue` etc.
    remain unchanged.
- **`misere.rs`** — checked misère-play outcomes (`try_misere_is_n`/`misere_is_p`)
  for finite acyclic impartial games; cycles return `None`. Covers misère Nim vs
  Bouton; the bounded indistinguishability quotient (`misere_quotient`,
  `AbstractGame`, `Quotient`); octal games (`octal_moves`, `octal_misere_quotient`).
  The non-linear route to the open question.
- **`lexicode.rs`****Bridge O**, the games ↔ integral edge: greedy binary
  lexicodes `L(n,d)` (Conway–Sloane 1986). `lexicode`/`lexicode_naive`/
  `lexicode_bounded` (+ `LEXICODE_NODE_BUDGET`, an honest backstop → `None`, not a
  silent cap). `LexicodeTurningGame` is the bounded Conway-Sloane move structure:
  positions are packed binary words, legal moves go to smaller words whose changed
  coordinate set has size `< d`, and the zero-Grundy positions are `L(n,d)`. The
  greedy step is exactly `mex(Forbidden)` over radius-`(d−1)` Hamming balls
  (`grundy::mex`); linearity is the Sprague–Grundy theorem, *discovered* not
  assumed. Ships the `[7,4,3]` Hamming, `[8,4,4]` extended Hamming, and `[24,12,8]`
  Golay codes as lexicodes, chaining `turning game → mex → lexicode → Golay →
  Construction A → theta`.
  Also ships `nim_lexicode_naive`/`NimLexicode`, the literal base-`2^k` greedy over
  nim alphabets: closure under coordinatewise nim-addition is verified, and scalar
  closure witnesses the Fermat-base line (base 4/16 pass, base 8 fails).
  **Claim level:** the degree-1 (solved, linear) side of `docs/OPEN.md` §1 — explicitly does
  NOT touch the open Gold-quadric question; do not cite as progress on it.

## The bridge object

- **`hackenbush.rs`** — red/blue/green Hackenbush: `Hackenbush { edges }` (vertex 0
  is the ground by convention; edges colored by the `Color {Blue, Red, Green}` enum)
  with the `string` stalk constructor, `to_game()` (the universal evaluator),
  `value()` → surreal (blue–red), `grundy()` → nimber (all-green = Nim). The one
  structure tying surreals + nimbers + sign-expansion through a single object.

## Things that look like bugs but are not (games layer)

- **`Game`, `LoopyValue`, `NumberGame`, and `NimberGame` `impl Display`** — that is
  the canonical render now. The old `display()`/`name()` inherent methods are retained
  as thin aliases over `Display`, so existing callers keep working; new code can just
  `{}`-format.
- **`Game::canonical_string` canonicalizes; `structural_string` does not.**
  `structural_string` is an order-independent fingerprint of the tree *as given* (so
  `(↑−↑).structural_string() ≠ 0`); `canonical_string` reduces first, so it *is* a
  value key. Compare `a.canonical().structural_eq(&b.canonical())` or just compare
  `canonical_string`s.
- **Atomic weight's integer branch is NOT `1 + max_R aw(G^R)`.** It's a predicate
  over `A`'s raw option *games* (`A^R = aw(G^R)+2`) comparing an integer `n` via
  `le`/`fuzzy`, bounded by the *tightest* right option — so it stays correct when an
  option's atomic weight is a fraction (e.g. ½). The naive max-of-integers form
  misreads there. And atomic weight IS additive on all-small games.
- **`nim_mul_mex` is the slow *game* definition (the mex recurrence), for validation
  and small arguments only** — exponential in the argument size. For real computation
  use the algebraic product (`nim_mul`), which it is proven equal to.
- **`nim_moves` takes `&Vec<u128>` (not `&[u128]`) on purpose** (with a `ptr_arg`
  allow): it is passed as a `fn` matching the generic move-generator bound `Fn(&P)`
  with `P = Vec<u128>` in `misere_is_p`/`grundy`, where a `fn(&[u128])` pointer would
  not unify.
- **`Game` stays an acyclic `Arc` tree by construction** (it cannot represent cycles).
  Loopy games are separate `LoopyGraph` / `LoopyPartizanGraph` engines;
  `thermography` is finite-game-only (loopy games never freeze to a number).
- **`Pl` does NOT implement `Semiring`.** A `Pl` wall has no representable ∞-wall
  (the tropical ``-identity), so the semiring law-checking lives on `Tropical<C>`
  (which has `Infinity`), not on `Pl`; `Pl` only gets the named wrappers
  `oplus_max`/`oplus_min`/`otimes` it actually uses. Do NOT fake an ∞-wall with empty
  `pts` — that breaks the `self.pts[0]` invariant `value_at` assumes. The
  `pub(crate)` `walls_with` (which internally calls `freeze`/`meeting_temperature`) is
  shared by `thermograph_via_tropical` so it reuses the identical cooling tail; the
  golden thermography tests pin that this sharing is inert.