Skip to main content

OCN — Open Chess Naming

CI Spec license: CC BY 4.0 Code license: MIT Release

OCN is a hierarchical, human-readable naming scheme for chess openings, designed as a companion to ECO (the Encyclopaedia of Chess Openings code system that has been the de facto standard since 1971). OCN keeps the best idea in ECO — the five structural families A/B/C/D/E — and replaces the arbitrary 100-codes-per-letter sub-numbering with short, parent-aware slugs.

At a glance

ECO OCN-1 Canonical name
B33 B.Sic.Sve Sicilian Sveshnikov
B90 B.Sic.Naj.Eng Najdorf English Attack
C67 C.RyL.Ber.Wal.End Ruy López, Berlin Wall Endgame
D47 D.Sem.Mer Semi-Slav, Meran
E97 E.KID.Cls.Mar KID Classical, Mar del Plata

Read once, remember forever. No lookup table needed.

Five-minute quickstart

pip install .           # from a checkout today
pip install ocn-chess   # from PyPI, with the next tagged release

The catalogue travels inside the package: no download, no database, no third-party dependency, and the lookups work on a plane.

from ocn import Catalog

cat = Catalog.load()                                 # the bundled catalogue, offline
cat.by_slug("B.Sic.Naj.Eng").canonical_name          # -> 'Sicilian Najdorf, English Attack'
[row.ocn1 for row in cat.parents("B.Sic.Naj.Eng")]   # -> ['B', 'B.Sic', 'B.Sic.Naj']
len(cat.by_eco("B90"))                               # -> 20
fen = "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2"   # 1.e4 c5
cat.by_fen(fen)[0].ocn1                              # -> 'B.Sic'

Twenty OCN rows share the single ECO code B90, and the breadcrumb reads the hierarchy back out — that is the whole pitch, in four lines. The FEN above ends in an en-passant square no pawn can legally capture on, which is what most board libraries print and what silently returns zero rows everywhere else; by_fen normalises it (see ocn/fen.py).

Then name your own games:

ocn annotate games.pgn --stats > named.pgn

Every game gains [OCN "..."] and [OCNName "..."] headers, matched by position at every ply — so a Najdorf reached through 1.Nf3 is named a Najdorf — and --stats prints the coverage summary to stderr. Nothing else in the file is touched.

The Python block above is executed and checked against its own # -> comments by tests/test_quickstart.py on every push: copy-paste works verbatim, or CI goes red.

Why

ECO has aged remarkably well in one respect: A/B/C/D/E captures the fundamental structural divide of chess openings (semi-open vs open vs closed vs Indian vs flank) and any new opening fits cleanly into one of those five families. OCN keeps that idea. It does not keep every one of ECO's letter assignments. 770 rows — 13.8% of the rows that carry an ECO code — sit in an OCN class that is not among their own ECO letters, always for a structural reason: the French is B, the London and Colle systems are A, the Grünfeld and Benoni are E. The complete machine-readable list is catalog/ocn-1.eco-divergence.tsv (derived, validator-enforced); the main cases are argued under "Borderline classifications" below and in the spec's Borderline rules.

ECO has aged poorly in another respect: the 00-99 sub-codes within each letter were assigned in 1971 according to what was fashionable at the time, distribute coverage unevenly (the Sicilian alone gets 80 codes, the irregulars share 19), and offer no parent-child hierarchy. Knowing that "B33 = Sveshnikov" or "C67 = Berlin Endgame" is a feat of memorisation that benefits no one — neither the player who wants to study an opening, nor the database designer running queries.

OCN replaces that 50-year-old sub-numbering with hierarchical slugs:

B              ← top level (structural family)
B.Sic          ← family (Sicilian)
B.Sic.Naj      ← variation (Najdorf)
B.Sic.Naj.Eng  ← sub-line (English Attack, 6.Be3)

For specific named tabiyas the slug may carry trailing SAN moves (B.Sic.Sve.Nd5 for the 11.Nd5 main line). See the spec for the full grammar; for everyday use the four levels above are enough.

You can read the slug at any depth and immediately know:

  • The structural class of the position (B = semi-open).
  • The family (Sic = Sicilian).
  • The variation (Naj = Najdorf).
  • And, if you want the precision, the exact tabiya down to the signature SAN move that defines it.

Format

<class> ( "." <named> )+ ( "." <move> )*
  • class: 1 char from A B C D E (ECO's five families; see "Borderline classifications" for where OCN's letter differs from ECO's).
  • named: one or more 3-char TitleCase tokens — family (Sic, RyL, KID), variation (Naj), subline (Eng) and deeper levels.
  • move: zero or more trailing SAN-style segments, capitalised pieces, check/mate stripped (Be3, e5, Bxf6, O-O).
  • Separator: dot ..
  • Maximum depth: 7 segments total (6 dots) — a hard cap; deeper theory is identified by position, not by longer slugs.

The full specification is in spec/OCN-1.md.

Status

Released — ocn-1.2.0 (2026-06-11). The format is stable; the reference catalogue has 5,899 entries, every duplicate-FEN group is resolved (unresolved_groups=0), canonical names carry their true diacritics, the ECO legacy codes are audited, and CI runs strict legal-move/SAN validation plus the full tool test suite under an unconditional gate. Release notes: docs/release-ocn-1.2.0-notes.md. Post-1.2 work adds internationalised alias sidecars and consumer tooling, planned in docs/traction-roadmap.md. Comments, corrections and additions welcome via issues.

Naming history: previously drafted as OCS — Open Chess Slug during alpha. Renamed to OCN before public release because "slug" carries different connotations outside web-development circles. The format itself is unchanged.

Catalogue

The reference catalogue lives in catalog/ocn-1.csv — 14 columns per row:

  • ocn1 — the OCN-1 string (primary key)
  • canonical_name — the canonical English name
  • eco_legacy — the ECO codes it covers
  • parent_ocn1 — the parent slug (nominal hierarchy)
  • moves_uci — the defining move sequence in UCI notation
  • depth — the depth in the hierarchy
  • aliases — known aliases (Sveshnikov a.k.a. Lasker–Pelikan)
  • flags — tags from the closed set gambit, sharp, closed, endgame, theoretical, deprecated
  • notes — free-text notes for borderline classifications
  • attributed_to, attribution_source, historical_notes — sourced naming attributions (who an opening is named for, with the citation; every non-empty attributed_to must carry a source)
  • transposes_to, same_as — position-identity relations (see "Three relations per slug" below)

The catalogue is licensed under CC-BY-4.0: you may use, share and adapt it for any purpose, including commercial, provided you cite "Club d'Escacs Figueres" and link to this repository.

Tools

Everything is Python 3 standard library — no third-party dependency, no build step (chess legality is checked by the in-repo move generator, tools/chess_uci.py). Recipes and join patterns for consumers are in docs/consuming-ocn.md.

The ocn-chess package

The installable form of everything below, with the catalogue bundled inside the wheel — no checkout, no network, no dependency. Built here under src/ocn/; pip install . from a checkout today, PyPI with the next tagged release.

from ocn import Catalog

cat = Catalog.load()
cat.by_slug("B.Sic.Naj.Eng").canonical_name   # 'Sicilian Najdorf, English Attack'
cat.by_eco("B90")                             # deepest first
cat.by_name("Grunfeld")                       # case- and diacritic-folded
cat.parents("B.Sic.Naj.Eng")                  # breadcrumb, root to parent
cat.by_fen(fen)                               # O(1); en-passant trap handled
ocn lookup B90
ocn lookup B.Sic.Naj
ocn fen "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2"
ocn uci "e2e4 c7c5 g1f3 d7d6"
ocn annotate games.pgn --stats > named.pgn

annotate reads a multi-game PGN (- for stdin), tags every game with [OCN] and [OCNName], and leaves the rest of the file byte for byte as it found it. The match is by position at every ply, deepest hit wins, so transpositions land on the same name as the move order they transpose into, and transposes_to is resolved to the canonical slug. --stats prints games, match rate, median depth in plies and the top openings to stderr; a thousand games annotate in well under a second.

Every subcommand takes --json. Details and join patterns: docs/consuming-ocn.md.

Consumer tools

  • tools/ocn.py — the in-repo reader: load the catalogue, look up a slug or a FEN, walk parents and children, resolve transposes_to and same_as. Use the package above unless you want a checkout-only script.
  • tools/from_uci.py — a legal UCI move sequence in, the deepest OCN-1 row whose moves are a prefix of it out (TSV, or --json).
  • tools/from_eco.py — an ECO code, a PGN file, or inline PGN with an [ECO "..."] tag in, the unique deepest match out; --all lists candidates for ambiguous codes.
  • tools/from_position.py — a FEN in, matching rows out; board, side to move, castling and en passant are matched, the move counters ignored.
  • tools/coverage_stat.py — runs the ocn annotate matcher over a PGN corpus and reports only the numbers: match rate, median depth, the depth table and the top openings, as text or JSON. The reproducible script behind any published "OCN names X% of real games" figure; it streams, so a compressed dump can be piped straight into it.
  • tools/export_positions.py — writes the derived position-indexed TSV/JSON view: fen_key, a complete fen with true halfmove/fullmove counters, transposition group size, SAN movetext, EPD and the Polyglot zobrist, all computed here in Python (roadmap H2.8). This is the index the package bundles.
  • tools/polyglot_zobrist.py — the Polyglot book hash of spec Annex A in stdlib Python: the public 781-key table vendored with its provenance, pinned in CI against the book format's published test vectors. No runtime dependency, no private repo in the chain.

Maintainer tools

tools/validate.py is the gate CI runs: format, slug collisions, parent references, depth limits, plus a --strict-chess mode for legal UCI sequences and SAN tail consistency. tools/audit_chess.py and tools/audit_transpositions.py are its batch counterparts, reporting every issue — or every duplicate-FEN group, ranked by how likely it is to need a structural decision — instead of stopping at the first; the resolution workflow is in docs/archive/transpositions.md. The attribution factory (audit_naming_attribution.py, candidate_slice_export.py, scaffold_attribution_manifest.py, apply_attribution_manifest.py) triages rows, exports review slices and applies evidence-backed JSON manifests under strict guardrails — the catalogue is never hand-edited (see docs/attribution-batch-engine.md). tools/fetch_lichess.sh pulls the upstream Lichess Opening Book TSVs (CC0) into external/. The remaining scripts — the Lichess parent map, the doc slug gate, and the test suite CI runs on every push (tools/tests/, .github/workflows/ci.yml) — are listed with the documents that use them in docs/INDEX.md.

Three relations per slug

Each catalogue row carries three relations to other slugs:

  • parent_ocn1 — nominal hierarchy. Groups slugs by literature lineage and lets a reader navigate from a family root down to a specific tabiya. The parent chain is what produces a readable slug (E.Nim.Rub.O-O.Nf3 is a child of E.Nim.Rub.O-O).
  • transposes_to — canonicalisation by position, asymmetric. Points from a slug whose FEN coincides with another slug's FEN to the FEN-canonical one. Set when this row is a move-order transposition of another. NULL when this row is itself canonical.
  • same_as — co-canonical preservation, symmetric. Pipe-separated list of slugs that share this row's FEN and are preserved as co-canonicals (both / all are real literary names — e.g. Rubinstein Opening ⇄ Colle-Zukertort). Mutually exclusive with transposes_to on a single row.

A position-indexed consumer (e.g. chess-parquet) should follow transposes_to once to canonicalise an OCN-1 result; rows linked by same_as are all canonical and may be returned together. A literature-oriented consumer (a book, a teaching tool) should follow parent_ocn1 to render the human hierarchy. The validator and audit_transpositions.py enforce that both transposes_to and same_as only point to rows whose FEN matches.

Compatibility with ECO

OCN-1 does not deprecate ECO. The catalogue records, for every entry, the ECO codes that the slug covers. Tools and consumers SHOULD support both:

  • given an ECO code, look up the OCN-1 slug;
  • given an OCN-1 slug, look up the ECO codes.

Books, ChessBase, Lichess Opening Book, FIDE rating reports, and any other system that uses ECO continues to work unchanged. OCN is the hierarchical layer on top.

Borderline classifications

Some openings sit awkwardly between ECO classes. OCN-1 makes explicit choices, and these are where its class letter differs from ECO's:

  • French (B.Fre and its subtree) is B, the semi-open class, although ECO codes it C00-C19. Rationale: OCN reads C as the symmetric king-pawn openings (1.e4 e5); the French answers 1.e4 asymmetrically, like the Sicilian and the Caro-Kann. At 252 rows this is the largest single divergence, and the one most worth arguing about.
  • London / Colle family (A.Lon, A.Col and neighbours) is A although ECO codes them in the D range (D02-D05). Rationale: they are queen's-pawn systems, played largely regardless of Black's reply, rather than Queen's Gambit theory (82 rows).
  • Catalan is D when Black plays ...d5 within the first five moves. Without ...d5, the position is E (Indian setup against the Catalan bishop).
  • Grünfeld is E (Indian) even though most legacy ECO codes place it in the D range (D70-D99). Rationale: Grünfeld is structurally an Indian defence, and grouping it with KID, Nimzo, QID, Bogo gives a cleaner parent-child hierarchy.
  • Benoni / Benko is E (Indian) even though legacy ECO places it in the A range. Rationale: the main Benoni and Benko families are Indian defences by structure and move-order (1.d4 Nf6 2.c4 c5), so they belong beside KID and Grünfeld rather than under flank openings.

See spec/OCN-1.md for the full reasoning.

Roadmap

The living plan is docs/traction-roadmap.md: five horizons — a public-ready gate, exist and install, prove it, announce, grow — taking OCN from a released catalogue to a standard people can find, install and cite. Two long-running tracks continue under it: naming attribution (are the eponyms true, and is the kind of attribution explicit — invented, published, popularised, event anchor? Methodology: docs/naming-attribution-audit-methodology.md) and the internationalised alias sidecars, where the English canonical_name stays definitive. Release records, decision logs and the rest of the documentation are indexed in docs/INDEX.md.

Acknowledgements

OCN-1 builds on:

  • Šahovski Informator (1971) for the A/B/C/D/E classification.
  • lichess-org/chess-openings (CC0) for canonical English names of the long tail.
  • Hooper & Whyld, Oxford Companion to Chess (1984), for naming conventions.

License

Dual-licensed:

  • Specification documents (in spec/) and catalogues (in catalog/): CC-BY-4.0.
  • Code (in tools/): MIT.

Copyright © 2026 Club d'Escacs Figueres.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ocn_chess-1.2.1.tar.gz (787.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ocn_chess-1.2.1-py3-none-any.whl (777.2 kB view details)

Uploaded Python 3

File details

Details for the file ocn_chess-1.2.1.tar.gz.

File metadata

  • Download URL: ocn_chess-1.2.1.tar.gz
  • Upload date:
  • Size: 787.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for ocn_chess-1.2.1.tar.gz
Algorithm Hash digest
SHA256 e372fa39d59301bfd3f6b7a33cf8777894ee1d82bd21302d961c0c691a820f6d
MD5 124ae712af2afa24da3ec09c50ff45f2
BLAKE2b-256 657d7df8338f6b6fecd0d2eb4ab299a7874203458700d121ef5077cd9351e480

See more details on using hashes here.

File details

Details for the file ocn_chess-1.2.1-py3-none-any.whl.

File metadata

  • Download URL: ocn_chess-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 777.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for ocn_chess-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fcd4ac0b4948f1d2c29e56fafdae63123b2e3c9b8f70dbc9968bdb1364838c03
MD5 7151315bded8fa27c7d24698cc74e27e
BLAKE2b-256 e2b1e4b5ecc0fa5fc9ca8e5efee0eac2a835069bbd66a38aaeba2ab7452ce6c7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page