Skip to content

lemonforest/chess-maths-the-movie

Repository files navigation

Chess Spectral Lattice Fermion Viewer

version spectralz

A drop-in spectral analysis instrument for chess corpora. Drop a .7z spectral corpus onto the page; the browser decompresses, parses, and indexes everything client-side, then renders a synchronized chessboard PGN replay, spectral lattice-fermion heatmaps (10 symmetry channels × 64 eigenmodes), channel energy traces, and an engine eval overlay.

The site is the instrument. The .7z is the specimen. There is no server, no build step, no pre-generated data directory.

The bytes this viewer renders are produced by the chess-spectral encoder (pip install chess-spectral — Python reference + byte-identical C17 port), part of the broader mlehaptics research programme that treats chess as a classical lattice fermion system — pieces as quantum-numbered particles on a grid-graph Laplacian, captures as field-energy redistribution on a shared rank-5 fiber bundle. See Producing corpora for the encoder CLI and Background for the theoretical framing.

Usage

  1. Open https://lemonforest.github.io/chess-maths-the-movie/ (or serve the repo root locally — see below).
  2. Click a bundled corpus card, drop a .7z onto the page, or click "browse".
  3. Step through plies with /, Home/End, Space to autoplay, 19/0 to switch games.

Run locally

python3 -m http.server 8000
# open http://localhost:8000

Any static file server works; there is no build step required to serve the site.

Bundled corpora

Any .7z dropped into dataset/ becomes a one-click card on the landing screen. After adding or removing a file, regenerate the manifest:

node scripts/build-dataset-index.mjs

This writes dataset/index.json, which the viewer fetches on load to populate the BUNDLED CORPORA list. Commit both the .7z and the regenerated index.json.

URL state

The viewer state is encoded in the URL fragment so positions are shareable:

#game=3&ply=42&view=A1&ch=A1,FT&scale=z

Loading a URL with a fragment but no corpus shows the drop zone with a hint; after dropping the matching corpus the viewer jumps to the requested state.

Fiber-norm overlay

Toggle the ∥F∥ button in the board panel header to paint the per-square rank-3 fiber norm for a chosen piece type as a gradient over the board. The field is a static property of the chess rules, derived from the shared fiber bundle in the research notebook §7. It does not change as you step through plies — this overlay is complementary to the existing per-channel overlay (⊞), not a replacement.

  • Pawn (P): direction-collapsed approximation — pawn moves are asymmetric (directional), so the "proper" piece Laplacian isn't symmetric. For display, we build a symmetrized adjacency from the union of both colours' one-square moves (vertical step + all four capture diagonals, no two-square advance). The resulting field has only Z2×Z2 symmetry (axis reflections + 180° rotation), NOT full D4 — 90° rotation would swap vertical advances with horizontal moves, which pawns don't have. Useful as a visual, but mathematically distinct from the other pieces; the verification gates exercise the smaller symmetry group.
  • Knight (N): center-bright, corners-dim. Matches the §7 qualitative 2:1 corner/center structure.
  • Bishop (B): diagonal-axis symmetric.
  • Rook (R): identically zero (rook rule content is in the diagonal channel, not the off-diagonal fiber; see §7b). The overlay paints a uniform neutral tint and displays a short helper note — this is expected, not a bug.
  • Queen (Q): proportional to bishop (queen = bishop + rook, and rook's off-diagonal contribution is zero; the bishop-queen cosine is 1.000 by construction).
  • King (K): localized pattern reflecting the king's 8-neighbourhood rule.

What is the field actually showing?

The overlay is a static property of each piece's move rules on an 8×8 board — it does not depend on the current position, the occupied squares, or the ply. If you toggle between ply 2 and ply 3 with N selected, the overlay looks identical, and that's correct. Stepping through moves changes where the knights are; it does not change how a knight moves from any given square, and this overlay visualizes the latter.

The most intuitive way to read it: at each square, the field measures how much "rule content" that piece carries if you placed it there, measured specifically through the shared rank-3 fiber (cross-mode coupling) of the board Laplacian. Translated into chess terms:

  • Knight. Has 2 legal targets from a1 (→ b3, c2), 3 from a2, 4 from a3/c1, 6 from b3, and 8 from d4/d5/e4/e5. The overlay shines brightest on the central 4×4 because those squares maximise knight connectivity; corners are dim because they minimise it. This is the canonical §7 example — the "2:1 corner/center" shape the notebook anchors on.
  • Bishop. Every bishop sees at most two diagonals from any square. On the central d4/d5/e4/e5 squares those diagonals are 13 squares long (7 + 7 + 7 + 7 intersected); on the corners they cover only 7 squares (one long diagonal). So the field brightens towards the centre along the diagonal axes.
  • Rook. Every rook sees exactly 14 squares (7 along its rank + 7 along its file) from anywhere on the board — dimension- invariant. That uniformity means all of the rook's "rule content" lives in the diagonal (D₄-symmetric) spectral channel, and none in the off-diagonal fiber. Hence the overlay is flat zero and the helper line points at §7b for the proof.
  • Queen. The queen's moves are the union of rook + bishop, but the rook piece is fiber-invisible (see above), so only the bishop part of the queen's rule set shows up in the fiber. Numerically this means the bishop and queen fields are parallel as 64-vectors (cosine = 1.000), just at a different overall scale — the viewer's per-piece range normalisation maps both to the full colour ramp so the shape is visible even though the absolute brightness differs.
  • King. A king has 3 legal targets in the corner, 5 along an edge, and 8 in the interior. That gives a much gentler corner→centre gradient than the knight (3:8 vs. 2:8) and also fewer long-range couplings, so the king field is both smaller in range and more localised than the knight's.

The easy first sanity check from a chess perspective: count the legal moves of the selected piece from a corner square vs. a central square in your head (or on paper). If the overlay is darker where you counted fewer moves and brighter where you counted more — for N, B, Q, K — it's working. R is the principled exception.

The sub-controls that appear when the overlay is on:

Control Options Effect
piece P N B R Q K which piece type to paint (pawn is direction-collapsed — see above)
render smooth / tiles bilinear-upsampled canvas gradient vs. discrete per-square tiles
colormap viridis / div / mono perceptually uniform / divergent-around-mean / greyscale elevation

Additionally, a follow ⇝ button appears in the chess-control row (next to the flip ⇅ button) while the fiber overlay is on. When it's active, stepping through plies auto-switches the piece selector to match whoever just moved: castling resolves to K (O-O and O-O-O), pawn moves (including captures and promotions) resolve to P, everything else reads off the leading SAN letter. On the starting position the selector holds whatever was last chosen. Turning follow on also suppresses the rook-helper note — with follow, the R piece is just one of many that'll be selected in passing, so the long explanation would be noise; the R button's native hover tooltip still carries the same text if it's wanted.

Combining with the channel overlay

The fiber overlay (∥F∥) and the channel overlay () can be on at the same time. They occupy different layers — fiber paints onto a dedicated <canvas> that sits above the board squares but below the piece sprites; channel paints each square's background-image directly — so they compose into a two-channel visualization:

  • fiber = ambient elevation (static, tells you "where does this piece's rule content live on an 8×8 board?")
  • channel = localised signal spikes (dynamic, tells you "what is this piece's spectral channel doing right now?")

When both are on the fiber canvas auto-dims from alpha 0.72 to 0.42 so the channel tints stay readable through it. Pick the mono colormap for the cleanest compose — its greyscale ramp stays out of the channel palette's cyan/amber hue zone, so the fiber reads as pure brightness and the channel keeps all the chroma to itself.

The fiber's tiles mode does genuinely conflict with the channel overlay (both paint the same background-image property), so turning channel on while fiber is in tiles auto-promotes fiber to smooth, and picking tiles while channel is on auto-disables channel.

Range normalization is per piece, so each piece's [min, max] maps to the full color scale independently — this preserves visual detail across pieces with different absolute magnitudes. The URL hash carries fiber=<piece>,<mode>,<cmap> when the overlay is on (with an extra ,follow suffix when auto-follow is enabled), so you can share a specific view.

Regenerating the fiber data

The per-square values live in data/fiber_norms.json and are produced offline by a single script:

pip install -e '.[fiber]'              # numpy
python3 scripts/generate_fiber_norms.py

The script builds the 8×8 grid Laplacian in the tensor-product eigenbasis, derives the rank-3 shared-fiber basis orthogonal to rook's per-square fluctuations, and writes the 6 × 64 field with per-piece range metadata. The V3 basis is derived from N/B/Q/K only; pawn is projected onto the same V3 afterwards so existing N/B/Q/K values stay stable. Verification gates enforced on write (and re-checked by pytest -q tests/test_fiber_norms.py):

  • rook field is identically zero (tol 1e-9)
  • each full-symmetry piece (N/B/R/Q/K) is D4-invariant under the 8 symmetries of the square
  • pawn is Z2×Z2-invariant but NOT D4-invariant (direction-collapsed adjacency — see the pawn bullet above)
  • bishop-queen cosine > 0.999999
  • knight corner a1 < center d4

Corpus format

Each corpus is a .7z archive containing:

Path Description
manifest.json Master index: games list, file paths, channel means.
pgn/game_NNN.pgn Standard PGN with inline [%eval] and [%clk].
ndjson/game_NNN.ndjson Per-ply records: FEN, SAN, UCI, eval, clock.
spectralz/game_NNN.spectralz Gzip-compressed binary eigenmode matrix.

.spectralz binary layout

HEADER (256 bytes):
  bytes  0– 7  ASCII "LARTPSEC"
  bytes  8–11  uint32 LE   version (currently 2)
  bytes 12–15  uint32 LE   dimensionality (640 = 10 channels × 64 modes)
  bytes 16–19  uint32 LE   stride per ply record (2568 bytes)
  bytes 20–23  uint32 LE   number of plies
  bytes 24–255 zero padding

PLY RECORDS (n_plies × 2568 bytes each, starting at byte 256):
  642 float32 LE values per record
    floats   0–639  spectral eigenmode values (10 channels × 64 modes)
    floats 640–641  reserved padding (ignored)

Channel index → (id, label, semantics):

idx id label semantics
0 A1 A₁ D₄-invariant singlet (rotational complexity)
1 A2 A₂ D₄ antisymmetric singlet
2 B1 B₁ diagonal-reflection symmetry
3 B2 B₂ anti-diagonal reflection symmetry
4 E E 2-D irrep — total board energy
5 F1 F₁ fiber 1 — cross-piece interaction
6 F2 F₂ fiber 2
7 F3 F₃ fiber 3
8 FA F_A pawn antisymmetric (Z₂ symmetry breaking)
9 FD F_D fiber determinant — interaction topology

Per-channel energy at a ply = Σ (eigenmode value)² over its 64 modes. Total fiber energy (F_T) = E(F₁) + E(F₂) + E(F₃). Chaos ratio χ (per game) = ⟨F_T⟩ / ⟨A₁⟩.

Producing corpora

This repo is a consumer of .7z corpus archives — it does not produce them. The encoder that makes the .spectralz files inside those archives is published on PyPI:

chess-spectral on PyPI

pip install "chess-spectral[corpus]"

The [corpus] extra pulls in python-chess for PGN ingest via the chess_spectral.corpus module. Source, C17 port, and a parity test suite that keeps the two implementations byte-identical live in the sibling mlehaptics repo — install from there instead if you want the C binary (µs/encode batch throughput) or to develop new channels.

CLI

After install, chess-spectral is on your $PATH:

Command Purpose
chess-spectral encode-fen --fen "<fen>" -o out.spectral Encode a single position to a 1-frame file.
chess-spectral encode {-i game.ndjson | --pgn game.pgn | -u <lichess/chess.com URL>} -o game.spectralz -z Encode a game to a gzip-compressed .spectralz. Accepts pre-produced NDJSON, a local PGN, or a URL that returns PGN text.
chess-spectral csv game.spectralz [-o game.csv] Emit the 17-column chat-friendly CSV (inter-frame metrics + channel energies). Auto-picks up a sibling .ndjson for eval/clk/NAG columns.
chess-spectral corpus --pgn FILE [FILE ...] [--run-id NAME] [--encoder {py,c}] Wrap one or more local PGNs into a viewer-ready folder (manifest.json + corpus_index.csv + corpus_summary.md + pgn/ + ndjson/ + spectralz/). --encoder c uses the C binary at $CS_SPECTRAL_BIN for ~38× throughput, byte-identical output.
chess-spectral version Print file-format / encoding-dim info.

Run any subcommand with --help for the full flag set — names and defaults in the CLI are the source of truth.

Packaging a viewer-ready .7z

chess-spectral corpus emits a folder (manifest.json + corpus_index.csv + corpus_summary.md + pgn/ + ndjson/ + spectralz/); the viewer expects a .7z archive, so you have to compress it yourself as a final step:

chess-spectral corpus --pgn my_games.pgn --run-id my_corpus --results-root .
7z a my_corpus.7z my_corpus/
# → drop my_corpus.7z onto the viewer

End-to-end from Lichess

run_corpus_sweep.py in the mlehaptics repo wires fetch → encode → feature-extract into one step:

python docs/chess-maths/run_corpus_sweep.py \
    --source lichess --username DrNykterstein --n 10 \
    --run-id lichess_drnykterstein_$(date +%Y-%m-%d)_N10
# → results/sweep_<run-id>/{manifest.json,pgn/,ndjson/,spectralz/,corpus_index.csv}

7z a sweep_lichess_drnykterstein_$(date +%Y-%m-%d)_N10.7z \
    results/sweep_lichess_drnykterstein_$(date +%Y-%m-%d)_N10/
# ↑ manual archive step — the encoder never writes .7z itself.

See ENCODERS.md for the full reproduction recipe, encoder lineage, and channel-layout reference.

Background

The 10 channels above are not a feature-engineering choice — they are the irreducible components of the 8×8 board Laplacian under D₄ symmetry (A₁, A₂, B₁, B₂, E) plus three shared off-diagonal fiber modes (F₁–F₃) and two pawn-specific channels (F_A antisymmetric, F_D diagonal deviation). Every piece type is uniquely classified by a 5-tuple of spectral quantum numbers; captures decompose into movement + annihilation + cross-term with exact charge-conjugation signature.

Full theoretical treatment, proofs, and computational verification: CHESS_SPECTRAL_INSTRUCTIONS.md and chess_spectral_research_notebook.md in the mlehaptics repo.

Architecture

Pure static site. No framework, no bundler, no Node.

chess-maths-viewer/
├── index.html            Entry point, drop zone, viewer shell
├── css/viewer.css        Dark scientific-instrument theme
├── js/
│   ├── app.js            State store, pub/sub, keyboard, URL hash, table, chain
│   ├── loader.js         .7z extraction, .spectralz parser, NDJSON, manifest
│   ├── opfs.js           Origin Private File System cache for per-game entries
│   ├── board.js          chessboard.js driver, FEN sync, playback
│   ├── chess-overlay.js  Paints per-square channel tint into chessboard.js squares
│   ├── fiber-overlay.js  Static rank-3 fiber-norm overlay (per piece type)
│   ├── othello-board.js  SVG driver for Othello corpora (swap-in for board.js)
│   ├── spectral.js       Channel registry, canvas heatmap renderer
│   ├── charts.js         D3 line chart, eval overlay, crosshair tooltip
│   ├── lru.js            LRU eviction for parsed game state
│   └── virtual-table.js  Virtual scroller for the corpus table
├── data/                 Static assets generated offline (fiber_norms.json)
├── dataset/              Bundled .7z corpora + generated index.json
├── scripts/              Dev utilities (run with node ≥18)
└── lib/                  Vendored JS libraries

External dependencies (CDN, no install required):

  • libarchive.js 2.0.2.7z decompression
  • Native DecompressionStream('gzip').spectralz decompression
  • chessboard.js 1.0 — board rendering (FEN-driven; chess.js is not loaded — positions come straight from per-ply FENs in the NDJSON)
  • jQuery 3.7.1 — required by chessboard.js 1.0
  • D3 v7 — scales, axes, line generators

Tests

Two test suites run in CI (see .github/workflows/ci.yml):

  • Pythonpytest -q exercises the bundled othello library (tests/test_board.py, tests/test_svg.py) and the fiber-norm data file (tests/test_fiber_norms.py — rook-is-zero, D4 symmetry, bishop-queen parallel, range metadata consistency). Requires the dev extra: pip install -e '.[dev]'.
  • JavaScriptnpm test runs a vitest suite under tests-js/:
    • lru.test.js — eviction order, pin safety, error tolerance of js/lru.js.
    • spectral.test.jschannelEnergyForPly, getOverlayForPly across the four overlay transforms (abs / Δply / log / z), parseEvalString, divergingColor.
    • virtual-table.test.js — jsdom-backed check that the virtual scroller renders fewer rows than the full dataset and keeps .active exclusive to the matching key.
    • opfs.test.js — Map-backed OPFS polyfill exercises isOpfsAvailable, cache-key derivation, and read/write round-trip of js/opfs.js.
    • smoke-large-corpus.test.js — reproduces the last-click-loses race on a synthetic 191 MB-shaped corpus by calling the real ensureGameData / LRU / virtual-table paths with a hand-verified 10-ply fixture (libarchive.js + WASM don't run under jsdom).

Install the dev dependencies with npm install; they stay under node_modules/ and are not loaded by the viewer at runtime.

License

See LICENSE.

About

In-browser spectral-analysis instrument for chess corpora produced by the chess-spectral encoder. Drop a .7z, get synchronized PGN replay, 10-channel lattice-fermion eigenmode heatmaps, energy traces, and engine-eval overlay. No server, no build step.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors