use ogdoad::forms::fit_f2_quadratic;
use ogdoad::scalar::nim_add;
mod common;
use common::{gold, p_set, polar};
fn agreement(p: &[u128], zero: &[u128], n: usize) -> usize {
let ps: std::collections::HashSet<u128> = p.iter().copied().collect();
let zs: std::collections::HashSet<u128> = zero.iter().copied().collect();
(0..n as u128)
.filter(|v| ps.contains(v) == zs.contains(v))
.count()
}
fn describe_pset(label: &str, p: &[u128], zero: &[u128], draws: usize, m: u128) {
let n = 1usize << m;
let agree = agreement(p, zero, n);
let plen = p.len();
let bias = plen as i128 - (n as i128 / 2);
print!(
" {label:<26} |P|={plen:<3} draws={draws:<3} agree {agree}/{n} with {{Q=0}} bias={bias:+}"
);
if p == zero {
println!(" ← EXACTLY {{Q=0}}!");
} else {
match fit_f2_quadratic(p, m as usize) {
Some(f) if f.is_genuinely_quadratic() => {
println!(
" P-set is a quadric (Arf={}, rank={})",
f.arf.arf, f.arf.rank
)
}
Some(_) => println!(" P-set is affine/linear (a subspace coset)"),
None => println!(" P-set is not a quadric"),
}
}
}
fn main() {
let (m, a) = (4u128, 1u128);
let n = 1usize << m;
let zero: Vec<u128> = (0..n as u128).filter(|&v| gold(v, a, m) == 0).collect();
println!(
"Gold form Q_{a} on F_2^{m}: |{{Q=0}}| = {} of {n}",
zero.len()
);
let zset: std::collections::HashSet<u128> = zero.iter().copied().collect();
let adhoc: Vec<Vec<usize>> = (0..n)
.map(|v| {
let vv = v as u128;
if zset.contains(&vv) {
(0..v).filter(|&w| !zset.contains(&(w as u128))).collect()
} else {
vec![0] }
})
.collect();
let (adhoc_p, _) = p_set(&adhoc);
println!(
"\n(a) hand-built acyclic game reproduces {{Q=0}} exactly: {}",
adhoc_p == zero
);
println!(" ⇒ existence is free; the open question is a NATURAL uniform rule.");
println!("\n(b) natural uniform rules (move v→w only for w<v, so the game terminates):");
let r1: Vec<Vec<usize>> = (0..n)
.map(|v| {
(0..v)
.filter(|&w| polar(v as u128, nim_add(v as u128, w as u128), a, m) == 1)
.collect()
})
.collect();
let (p1, d1) = p_set(&r1);
describe_pset("B-coupled descent", &p1, &zero, d1, m);
let r2: Vec<Vec<usize>> = (0..n)
.map(|v| {
(0..v)
.filter(|&w| gold(w as u128, a, m) != gold(v as u128, a, m))
.collect()
})
.collect();
let (p2, d2) = p_set(&r2);
describe_pset("Q-changing descent", &p2, &zero, d2, m);
let r3: Vec<Vec<usize>> = (0..n)
.map(|v| {
(0..m)
.filter(|&i| v as u128 & (1 << i) != 0)
.map(|i| (v as u128 ^ (1 << i)) as usize)
.filter(|&w| polar(v as u128, nim_add(v as u128, w as u128), a, m) == 1)
.collect()
})
.collect();
let (p3, d3) = p_set(&r3);
describe_pset("single-bit B-gated turn", &p3, &zero, d3, m);
println!("\nConclusion. One uniform rule — 'Q-changing descent' — reproduces {{Q=0}} exactly,");
println!("but ONLY because it references Q directly in the move legality (move iff you flip");
println!("Q): it bakes the form into the rules, so it is tautological, not a discovery.");
println!("The rules coupled through B — the polar form, which is the legitimately");
println!("game-realizable (coin-turning) ingredient — do NOT give {{Q=0}}: B-coupled descent");
println!("yields an affine subspace, and the single-bit B-gated turn yields a *different*");
println!("quadric (wrong Arf). So the sharp open question stands: a game whose moves are");
println!("built from the combinatorial ingredients (B / coin-turning) ALONE — not from Q");
println!("itself — with P-set {{Q=0}}. The kernel solver + fit_f2_quadratic are the test");
println!("bench; the gap is now precisely a B-only rule that integrates up to the Q-quadric.");
}