Reading the Board β€” a GF(256) integer model that learns Connect Four

A small neural model whose trunk does XOR and table lookups instead of float multiply-accumulates β€” and an honest measurement of how good it actually is.

Author: Christof Ckal

Gold is the human, blue is the model.*

How good is it, actually?

600 games per arm, against the released checkpoint:

Opponent Model's win rate Verdict
Random player 85.2 % beats it
Centre-column policy 90.5 % beats it head-to-head
1-ply heuristic 37.2 % loses to it
2-ply lookahead 24.3 % loses to it
Minimax 41.8 % loses to it

That gap is the point of the project. The model agrees with its teacher on 59.6 % of training positions and 41.0 % of a board-disjoint holdout β€” and it still loses to a three-line rule that only checks "can I win now?" and "can I block now?". Teacher-agreement and game strength are different things, and this repo measures both.

β–Ά Play it against the model

https://c4.tensorgarten.de

A real, session-isolated game β€” every visitor gets their own board. Deployed on CPU. Verified isolation + security claims.

Or play it inside the Hugging Face Space: https://huggingface.co/spaces/Chris4K/connect-four-gf256 (the Space is static and frames the live server; the server allows that origin explicitly β€” never a wildcard).


The dataset

The training and evaluation data is published as a separate dataset repository:

`Chris4K/connect-four-gf256`

git clone https://huggingface.co/datasets/Chris4K/connect-four-gf256
File Rows What
train/corpus_320k.txt 320,000 training games, v2 byte transcript (10 bytes per ply)
train/perply_targets.npz 320,000 per-ply 7-way targets β€” (N, 19, 7) float32, plus ply_of, teacher, n_plies
holdout/corpus.txt 4,000 board-disjoint holdout games
holdout/perply_targets.npz 4,000 the holdout's per-ply targets
SHA256SUMS β€” checksums for all four artifacts

Everything in it is synthetic: games generated by a search teacher playing itself. No personal data, no scraped text, no third-party content.

The labels come from two solvers, recorded per slot in the teacher array:

teacher Solver Share of labelled slots
1 bounded exact solver (max_empty=7) β€” returns the full tie set 47,209 (1.0 %)
2 negamax alpha-beta search teacher β€” returns one move 4,516,452 (99.0 %)

The exact solver's slots are the only multi-label ones (65.6 % of them spread mass over several equally-best moves); the alpha-beta teacher's targets are one-hot.

The teacher is a search policy, not a proof of optimality. Connect Four is a solved game, but this model was never taught the full solution β€” it imitates a bounded search. No label in the dataset should be read as "the set of optimal moves".

The published corpus is the repaired one. An earlier corpus had a corrupt trailing field in 126,537 of 320,000 rows (39.5 %); the trainer now refuses such a corpus outright.

The model

The trunk is a GF(2⁸) integer program: XOR, learned S-box table lookups and integer adds. No floating-point multiply-accumulate in the trunk.

board ──► byte transcript ──► GF(256) trunk ──► read-out ──► column logits
           (v2 format)         (XOR + S-box)   (float32)      (7 columns)
  • Layers: 3 Β· d: 32 Β· lags: [0,1,2,3,4,5,6,7,8,9,10,20]
  • Context: 192 Β· Batch: 16 Β· Seed: 32
  • Params: emb 8,192 Β· coef 288 Β· sbox 24,576 Β· wiring 576 Β· readout 8,388,608 Β· bias 256
  • Size: 32.3 MB, 8,422,496 parameters, CPU-only inference.

Honest scoping of "zero FLOPs": the trunk has no floating-point multiply-accumulate. The read-out is float32 and is 99.6 % of the parameters. The complete model is therefore not a zero-FLOP program.

Supervision

Every ply of every training game is labelled with the move chosen by a negamax alpha-beta search teacher, with ties preserved as sets rather than collapsed. The exact data β€” corpus, per-ply targets and the board-disjoint holdout β€” is published in the dataset repository (see The dataset above).

The teacher is a search policy, not a proof of optimality. Connect Four is a solved game, but this model was never taught the full solution β€” it imitates a bounded search. The repo's src/c4/exact_solver.py is a separate, bounded endgame solver (max_empty=7); it supplied 1.0 % of the corpus labels and check positions, it did not label the corpus.

Results

The convergence curve

Step Teacher agreement (train) Holdout (board-disjoint) vs 1-ply heuristic vs random
0 16.3 % 11.9 % 9-39 36-12
2000 40.2 % 27.9 % 10-38 37-11
6000 50.9 % 36.7 % 15-33 38-10
10000 56.0 % 40.8 % 19-29 37-11
14000 57.5 % 38.1 % 23-25 44-4
18000 59.6 % 41.0 % 16-32 41-7
26000 58.7 % 39.1 % 15-33 38-10
30000 59.4 % 42.0 % 18-30 38-10
34000 58.5 % 41.8 % 14-34 43-5

What the model learned

  • It reads the board. Its picks spread across all seven columns (holdout: [39, 55, 94, 175, 78, 48, 23]), with non-zero per-column recall for every column. It is not a degenerate constant policy.
  • It beats random 85.2 % of the time (n=600, 3 seeds).
  • It agrees with the teacher on 59.6 % of training positions and 41.0 % of held-out positions.

What it did not learn

  • It does not beat a 1-ply heuristic. Net-negative at every measured step of the entire run. The best point is 23-25 β€” near parity, never ahead.
  • Agreement plateaus after ~step 14000. More steps stopped helping.
  • The ~18 pp train/holdout gap shows a real memorisation component.

Independent strength check

The canonical src.c4.duel harness, 600 games per arm per seed Γ— 3 seeds, model greedy (Ξ΅ = 0), opponent explores (Ξ΅ = 0.3) so trajectories are genuinely distinct:

  • vs. random: W1460 L337 D3 of 1800 (p β‰ˆ 3.2 Γ— 10⁻¹⁢⁢)
  • vs. the best single-column constant: wins in 2 of 5 opponent settings β€” not a sweep
  • seat check: model gap +7.5 pp, heuristic gap +4.8 pp, gap-vs-gap p = 0.402 β€” no model-specific seat asymmetry

Read the model yourself

The checkpoint is hosted on Hugging Face rather than committed to this repository, so a clone stays small:

# 1. Download the checkpoint (32.3 MB)
curl -L -o best_hard.pt \
  https://huggingface.co/Chris4K/connect-four-gf256/resolve/main/best_hard.pt
# (or: huggingface-cli download Chris4K/connect-four-gf256 best_hard.pt)

# 2. Verify it before using it
sha256sum best_hard.pt
# 134035637b637f87f3837aa8929dc5ec5db3bb0f0c9526f93fe1449be77672e2

# 3. Play it (the server takes --model; --checkpoint does not exist)
#    Run this from a clone of https://github.com/Ckal/connect-four-gf256
python -m src.c4.ui_server --model best_hard.pt --port 7860 --device cpu
# open http://127.0.0.1/

best_hard.pt is the step-18,000 hard export. The UI shows the model's next-move distribution over all seven columns (rendered from the hard path), the legal-move set, and lets you undo and reset. The 100 % figure on a column is a probability of being optimal, not a chance of winning.

The public deployment uses per-session boards. The upstream research UI keeps a single board in a module global and is not safe for multi-user hosting as shipped.

Security verification

The public deployment is gated by a script that runs against the deployed process over public HTTPS β€” not against a local server.

git clone https://github.com/Ckal/connect-four-gf256.git
python scripts/verify_public_isolation.py https://c4.tensorgarten.de
# PASSED: 19   FAILED: 0

Embedding (the Hugging Face Space)

The Space at Chris4K/connect-four-gf256 is static β€” it contains only a page that frames the live server. The server permits exactly the framing origins it needs:

Content-Security-Policy: …; frame-ancestors https://chris4k-connect-four-gf256.static.hf.space https://huggingface.co

The allowlist is explicit and closed:

  • Default is deny. With no --allow-embedding flag the response carries X-Frame-Options: DENY and frame-ancestors 'none'.
  • Only https:// origins are accepted. An http:// ancestor could be intercepted; a https://* wildcard is refused outright, because it would let any site clickjack the game.
  • The allowlist replaces X-Frame-Options when it is in force β€” XFO can only allow or deny a single origin, while CSP can name several.
  • State-changing requests are origin-checked. Because the session cookie must be SameSite=None to work inside a cross-site frame, the server validates the Origin header on every mutating request instead of relying on SameSite=Lax for CSRF protection. A request from an origin that is not the site or an allowlisted ancestor is refused with HTTP 403.

Run it yourself:

python -m src.c4.ui_server --model best_hard.pt --public \
    --allow-embedding https://chris4k-connect-four-gf256.static.hf.space

It asserts, and all of these currently hold:

Assertion Result
GET / returns 200 with a real page PASS
Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, HSTS PASS
Two clients receive different session cookies PASS
The session cookie is HttpOnly and Secure PASS
A move in one session is invisible in another β€” no shared board PASS
/modelinfo and /reload are closed in public mode (HTTP 403) PASS
A mutating request from a non-allowlisted origin is refused (HTTP 403) PASS

The session cookie as served when cross-site embedding is enabled:

Set-Cookie: tg_sid=<opaque id>; Path=/; HttpOnly; SameSite=None; Secure; Max-Age=7200

Without an embedding allowlist it is SameSite=Lax; Secure.

The checkpoint served by the public container is byte-identical to the one on Hugging Face β€” SHA-256 134035637b637f87f3837aa8929dc5ec5db3bb0f0c9526f93fe1449be77672e2.

Reproduce it

The source, tests and measurement scripts are in the GitHub repository:

git clone https://github.com/Ckal/connect-four-gf256.git
cd connect-four-gf256

# the data
git clone https://huggingface.co/datasets/Chris4K/connect-four-gf256.git

pip install --extra-index-url https://download.pytorch.org/whl/cpu -r requirements.txt

# Train (seed 32, 100k steps, batch 16, ctx 192)
V24_TAG=v24 bash run_v24.sh

# Score any checkpoint against its owner corpus
python -u tools/measure_promotion_scorer.py \
  --corpus-run runs/_probe/v24 \
  runs/_probe/v24/checkpoints/best_hard.pt

# Full regression suite
bash tools/run_regression_sweep.sh

The scorer refuses to guess which corpus belongs to a checkpoint: pass --corpus-run explicitly, or let it find an unambiguous ancestor owner run. Without one it exits non-zero rather than scoring against the wrong data.

Every published number traces to a command and an artifact β€” see RESULTS.md.

Bugs that had to be fixed first

This is the part most write-ups skip. Each of these produced a plausible-looking number that was wrong.

# Bug Impact
1 Terminal-state check truthy on terminal boards Wrong game-over detection
2 Corrupt trailing field in 126,537 / 320,000 rows (39.5 %) 1,567,958 affected plies; trainer now rejects corrupt corpora
3 Sidecar read by batch position instead of sampled corpus row ~41.3 % of supervised positions had all-zero targets
4 Promotion key = lowest byte-bpb Selected step 1500 over the final step 3923
5 Final exports omitted transcript version Read as v1 β†’ scored 24.4 % instead of 91.3 %
6 Historical per-ply index arithmetic Early positions t < k assigned to row kβˆ’1

What this invalidates. The project's earlier conclusions β€” "dense per-ply supervision is worse", "mirror consistency is net negative", "symmetry is not worth it" β€” were all drawn before the row-index repair. They are not evidence about the corrected model and are not cited in the paper.

Fixing this also invalidated the old "always play column 3" control: it had actually been a centre-first staircase, not a constant policy.

Reproducibility guarantees

Property Evidence
Bit-exact resume max weight Ξ” 0.000e+00, identical Adam moments (12/12 checks)
Full regression suite 27 PASS, 0 FAIL β€” reproduced by independent sweeps
Live UI parity 40/40 played columns, next-move distribution L1 = 0.000e+00
Atomic checkpoint publication 9/9 checks; 0 corrupt reads in 769 reads
Transcript stamping 3/3 checks

Component suites: live parity v3/v4/v5 Β· UI hot reload 14/14 Β· resume state 19/19 Β· resume equivalence 12/12 Β· single-server UI 8/8 Β· atomic save 9/9 Β· transcript stamp 3/3 Β· watcher 12/12 Β· sessions 47/47 Β· embedding + origin policy 18/18.

Layout

The full source tree lives in the GitHub repository β€” this model repository holds only the checkpoint, the paper and the card.

.
β”œβ”€β”€ README.md                  this file
β”œβ”€β”€ CONNECT_FOUR_PAPER.md     the Connect Four manuscript
β”œβ”€β”€ LICENSE                    MIT
β”œβ”€β”€ best_hard.pt               the checkpoint (step 18,000), 32.3 MB
└── .gitattributes

Everything else β€” source, tests, papers, measurement scripts, the demo recording β€” lives in the GitHub repository (https://github.com/Ckal/connect-four-gf256), and the training data lives in the dataset repository (https://huggingface.co/datasets/Chris4K/connect-four-gf256).

The source tree, for reference:

src/ β”œβ”€β”€ c4/ β”‚ β”œβ”€β”€ train.py trainer: dense per-ply supervision, holdout, promotion β”‚ β”œβ”€β”€ agent.py hard export, prompts, greedy policy, legal masking β”‚ β”œβ”€β”€ board.py board + legal moves + terminal check β”‚ β”œβ”€β”€ duel.py canonical strength harness β”‚ β”œβ”€β”€ opponents.py random / centre / heuristic / lookahead / minimax β”‚ β”œβ”€β”€ transcript_v2.py canonical transcript decoder β”‚ β”œβ”€β”€ corpus_integrity.py corpus + transcript validation β”‚ β”œβ”€β”€ sessions.py per-session boards and cookies β”‚ └── ui_server.py the game UI (see the multi-user note above) β”œβ”€β”€ gf256_markov_lm_v6.py the shipped architecture └── generate.py text-generation driver

tests/ the regression suite tools/ the authoritative measurement scripts scripts/verify_public_isolation.py the public deployment gate docs/ the full research record


## Limitations

1. **Net-negative vs. the 1-ply heuristic** β€” the headline negative result.
2. **Teacher-agreement β‰  game strength** β€” the central finding.
3. **Holdout solver coverage is 37.3 %** (560/1,500). The other 940
   positions have no proved label and are excluded, not guessed.
4. **~18 pp train/holdout gap** indicates memorisation in the headline
   number.
5. **Architecture-level superiority is unproven** β€” the controlled A/B arms
   reached only 0.30 epoch-equivalents and are provisional.
6. **Mirror/symmetry questions remain open** for the corrected model.

## Citation

```bibtex
@misc{ckal2026gf256_connectfour,
  title  = {Reading the Board: A GF(256) Integer Model for Connect Four},
  author = {Ckal, Christof},
  year   = {2026},
  url    = {https://github.com/Ckal/connect-four-gf256}
}

License

Code: MIT (see LICENSE). Paper text: Β© 2026 Christof Ckal. Model weights: MIT.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Space using Chris4K/connect-four-gf256 1