Help us shape the future of Go! Take the 2026 Go Developer Survey

ogxmparser

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 14 Imported by: 0

README

ogxmparser

Go package to parse OGXM match files and OGID position IDs (OpenGammon).

OGXM is the match format of HedgeHog / OpenGammon: the moves of a backgammon match, any number of engine analyses of them (checker plays with ranked alternatives, cube decisions, luck), and Ed25519 signatures over the match and over each analysis. OGID is its position identifier, e.g. 11ccccchhhjjjjj:66666888dddddoo:N0N:46:B:IB:0:0:3:0.

f, err := ogxmparser.ParseFile("match.ogxm")
if err != nil { ... }
fmt.Println(f.Match.WhiteName, "vs", f.Match.BlackName, f.Match.Length)

for gi, g := range f.Replay() {          // positions, as OGIDs
    for pi, ply := range g.Plies {
        fmt.Println(gi, pi, ply.Before.String())
    }
}
for _, r := range f.VerifySignatures(nil) {
    fmt.Println(r.KeyID, r.Status)       // valid | invalid | unverifiable
}

o, err := ogid.Parse("cccccggggg:ddddiiiiii:N0N:63:W:IW:4:3:7:1:15")
fmt.Println(o.ToMove(), o.CubeValue(), o.MatchLength, o.Crawford)

Specifications

Both formats are specified in HedgeHog's public repository, gitlab.com/eranlambooij/hedgehog-public: docs/OGXM_FORMAT_SPEC.md (v2.2) and docs/OGID.md, licensed CC BY 4.0. This package is an independent implementation written from them; where the prose leaves a detail open it follows the reference implementation in the same repository (MIT).

Implemented: the v2 container, records, every section's decoding, the hard errors a reader enforces, the verified values of section 11 (file size, CSUM CRC32 / SHA-256, score_final, match_digest), replay to positions (5.4) and Ed25519 verification of SIGN and MSIG.

Not implemented: writing; OGXM v1 files and .ogxms streams; the M4 check that a play is legal for its dice (each step is checked, M3); decoding of CLCK, VIDO and annotation drawings (kept raw or counted).

The OGID color field

The fifth field names the player who reached the position, so the other one is to move: ...:46:B:IB:... is White to play 4-6. OGID.ToMove applies this, as the reference parser does.

Tests and corpus

testdata/ holds the corpus and, for each file, .ref.json: what the reference implementation reads from it (every ply's OGID before and after, analysis counts, luck, each signature's verdict and signing payload). testdata/generate.py regenerates it from a build of the reference.

  • hedgehog-3pt-analysed.ogxm is a match exported by HedgeHog with its analysis and its production SIGN and MSIG, taken from the test data of AnkiGammon (MIT).
  • hedgehog-3pt-moves.ogxm and money-session.ogxm were written by the reference converter (ogxm_convert --to-binary) from the .src.json beside them; the money session exercises beaver, raccoon, a resignation, a last-game flag and a settlement.
go test ./...
go test -fuzz FuzzParse .

License

MIT, see LICENSE. The specifications this package implements are © Eran Lambooij, CC BY 4.0; the corpus file taken from AnkiGammon is MIT, © 2025 AnkiGammon: its licence is testdata/LICENSE-AnkiGammon.

Documentation

Overview

Package ogxmparser reads OGXM, the OpenGammon eXtensible Match format of HedgeHog / OpenGammon: a backgammon match stored as the moves that were played, plus engine analyses of those moves, optionally signed with Ed25519.

The format is specified in docs/OGXM_FORMAT_SPEC.md of HedgeHog's public repository (gitlab.com/eranlambooij/hedgehog-public), version 2.2, licensed CC BY 4.0. This package implements the reader side of that specification:

  • Parse walks the container (2), every record (3.2) and section (4-8), enforces the hard errors a reader must, and verifies what section 11 lists: file size, CSUM, score_final, a Combined file's match_digest.
  • File.Replay reconstructs every position (5.4) and expresses each as the OGID the reference implementation derives for it (package ogid).
  • File.VerifySignatures reports each SIGN and MSIG as valid, invalid or unverifiable (8.1, 8.6). A failed signature is never a load error, and a key carried by the file identifies its signer, it does not make it trustworthy.

What it does not do: write files; read OGXM v1, or streams (2.6) other than one file at a time; check M4 (that a recorded play is legal for its dice; Board.Apply checks each step, M3); decode CLCK, VIDO and drawings, which are kept raw or counted.

Index

Constants

View Source
const (
	ReaderMajor = 2
	ReaderMinor = 2
)

ReaderMajor and ReaderMinor are the format version this reader implements.

View Source
const (
	MaxFileSize        = 64 << 20
	MaxSectionSize     = 16 << 20
	MaxSections        = 4096
	MaxGames           = 1000
	MaxPliesPerGame    = 1500
	MaxTotalPlies      = 100000
	MaxAltsPerDecision = 1024
	MaxAnalyses        = 64
	MaxString          = 4096
	MaxAnnotations     = 4096
	MaxDrawings        = 256
	MaxDecisions       = 300000
	MaxCoverage        = 100000
	MaxMatchSignatures = 16
)

Limits of spec section 10 that this reader enforces.

View Source
const (
	ActionDouble      = 21
	ActionTake        = 22
	ActionDrop        = 23
	ActionGameOver    = 24
	ActionMatchOver   = 25
	ActionFinal       = 26
	ActionResignGame  = 27
	ActionResignMatch = 28
	ActionForfeit     = 29
	ActionNull        = 30
	ActionSetPosition = 31
	ActionBeaver      = 32
	ActionRaccoon     = 33
	ActionSettle      = 34
	ActionReserved    = 35
	ActionCubeSet     = 36
	ActionPass        = 37
)

Action ids of 9.14 beyond the dice rolls 0-20.

View Source
const EquityScale = 1e6

EquityScale is the fixed-point unit of the Equity and Equity loss kinds (3.3).

View Source
const ProbabilityScale = 1e4

ProbabilityScale is the fixed-point unit of the Probability kind (3.3).

Variables

View Source
var ErrNotOGXM = errors.New("ogxm: not an OGXM file")

ErrNotOGXM is returned when the data does not start with the OGXM magic.

View Source
var ErrVersion = errors.New("ogxm: unsupported version")

ErrVersion is returned for a file this reader is too old to read, or for a v1 file, which this reader does not convert.

Functions

func DiceAction

func DiceAction(d1, d2 int) int

DiceAction returns the action id of a roll (9.14).

func IsOGXM

func IsOGXM(data []byte) bool

IsOGXM reports whether data starts with the OGXM magic.

Types

type Alternative

type Alternative struct {
	Steps          []Step
	Equity         float64
	Probs          *Probabilities
	Level          *Level
	IsPlayed       bool
	RolloutSE      *float64
	CubelessEquity *float64
}

Alternative is one candidate play of a checker decision (7.2).

type Analysis

type Analysis struct {
	ID             [16]byte
	MatchDigest    *[32]byte
	Producer       *int // 9.9
	Level          *Level
	Complete       bool
	Coverage       []int // nil = every ply
	ModelID        string
	ModelName      string
	ModelDigest    *[32]byte
	EngineBuild    string
	Currency       Currency
	CubeEfficiency *float64
	METID          string
	Tables         string
	StartedAt      *time.Time
	CompletedAt    *time.Time
	DurationMS     *int
	Sources        [][16]byte
	Decisions      []Decision
	Signature      *Signature
	// contains filtered or unexported fields
}

Analysis is one analysis block: ANAL, its DECS and its optional SIGN (6-8.1).

type Annotation

type Annotation struct {
	Scope    int // 9.20
	Ref      int
	Value    string
	Key      *string
	Kind     *DecisionKind
	AltIndex *int
	Lang     string
	Author   string
	At       *time.Time
	Drawings int
	Analysis *[16]byte
}

Annotation is one ANNO record (8.4). Drawings are counted, not decoded.

type Board

type Board [26]int8

Board is 26 signed counts indexed by absolute point (M1): positive is White, negative Black; 0 is White's bar and 25 Black's.

func Opening

func Opening(v Variant) (Board, bool)

Opening returns the variant's opening position (5.2).

func (Board) Apply

func (b Board) Apply(seat Seat, steps []Step) (Board, error)

Apply plays the steps of seat in stored order (M3). It checks that each step moves a checker the seat holds, enters from the bar first, lands on a point the opponent does not hold, and bears off only with every checker home. It does not check M4 (that the play is a legal one for the dice).

func (*Board) Count

func (b *Board) Count(seat Seat) int

Count returns the checkers of seat on the board.

type CheckerDecision

type CheckerDecision struct {
	Alternatives      []Alternative
	AlternativesTotal *int
	BestEquity        *float64 // stored only without alternatives
	EquityLoss        *float64 // stored only when no alternative is played
	Level             *Level
	PositionTags      *uint64
	ProducerRef       *int
}

CheckerDecision is a CHECKER record (7.1).

type Checksum

type Checksum struct {
	Algorithm int // 9.15: 0 CRC32, 3 SHA-256
	Digest    []byte
	Verified  bool // false only when the algorithm is unknown to this reader
}

Checksum is the CSUM record (8.5).

type CubeDecision

type CubeDecision struct {
	Verdict          Verdict
	NoDoubleEquity   *float64
	DoubleTakeEquity *float64
	DoublePassEquity *float64
	Probs            *Probabilities
	EquityLoss       *float64
	TakePoint        *float64
	WindowSearched   bool
	Level            *Level
	IsOptional       bool
	IsFreeCube       bool
	CubefulTakeValue *float64
	Currency         *Currency // nil = the block's
	ProducerRef      *int
}

CubeDecision is a CUBE record (7.4).

type Currency

type Currency int

Currency is the Currency enum (9.10).

const (
	Cubeless      Currency = 0
	CubefulMoney  Currency = 1
	CubefulMatch  Currency = 2
	CurrencyUnset Currency = -1
)

type Decision

type Decision struct {
	PlyRef  int
	Kind    DecisionKind
	Checker *CheckerDecision
	Cube    *CubeDecision
	Resign  *ResignDecision
	Roll    *RollDecision
}

Decision is one DECS record (7). Exactly one of the kind pointers is set for a known kind; an unknown kind leaves all nil.

type DecisionKind

type DecisionKind int

DecisionKind is the decision kind enum (9.12).

const (
	KindChecker DecisionKind = 0
	KindCube    DecisionKind = 1
	KindResign  DecisionKind = 2
	KindRoll    DecisionKind = 3
)

type File

type File struct {
	VersionMajor, VersionMinor     uint16
	MinReaderMajor, MinReaderMinor uint16

	Match           *Match
	Games           []Game
	Analyses        []Analysis
	Annotations     []Annotation
	MatchSignatures []Signature
	Checksum        *Checksum
	// Clock and Video are the raw CLCK and VIDO payloads; this reader does not
	// interpret them (they are ancillary), but an MSIG may cover the clock.
	Clock []byte
	Video []byte
	// contains filtered or unexported fields
}

File is a parsed OGXM v2 file.

func Parse

func Parse(data []byte) (*File, error)

Parse parses one OGXM v2 file. It verifies everything section 11 lists as verified (file size, CSUM, score_final, a Combined file's match_digest, record walks) and the replay rules this reader implements (Replay); it does not verify signatures, which VerifySignatures reports on.

func ParseFile

func ParseFile(path string) (*File, error)

ParseFile reads and parses one OGXM file from disk.

func ParseReader

func ParseReader(r io.Reader) (*File, error)

ParseReader reads r to the end and parses it as one OGXM file.

func (*File) MatchDigest

func (f *File) MatchDigest() [32]byte

MatchDigest computes match_digest (6.1) over the stored MTCH and GAME payloads, each preceded by its length.

func (*File) PlyCount

func (f *File) PlyCount() int

PlyCount returns the number of plies across every game.

func (*File) Replay

func (f *File) Replay() []GameReplay

Replay reconstructs every game's positions (5.4).

func (*File) Shape

func (f *File) Shape() Shape

Shape returns the file's shape (2.5).

func (*File) VerifySignatures

func (f *File) VerifySignatures(keys KeyLookup) []SignatureReport

VerifySignatures checks every SIGN and MSIG (8.1, 8.6). A failed signature is reported, never a load error. keys may be nil.

type FormatError

type FormatError struct {
	Section string // four-character section type, empty for the container
	Offset  int    // byte offset within the section payload (or the file)
	Msg     string
}

FormatError is a hard error (section 0): the file is rejected.

func (*FormatError) Error

func (e *FormatError) Error() string

type Game

type Game struct {
	Winner           *Seat
	PointsWon        *int
	IsLastGame       bool
	InitialBoard     *Board // nil = the variant's opening position (5.2)
	InitialCubeValue int    // 1 when absent
	InitialCubeOwner Seat   // Centred when absent
	AutoDoubles      int
	Termination      *int // 9.7
	Plies            []Ply
}

Game is one GAME section (5).

type GameReplay

type GameReplay struct {
	Plies []PlyState // one per ply up to FailedAt
	// FailedAt is the index of the first ply that does not replay, or -1
	// (M9). Plies from it on have no position.
	FailedAt int
	Err      error
	Crawford bool
	// ScoreBefore is the score at the game's start, White then Black (M8).
	ScoreBefore [2]int
}

GameReplay is one game's replay.

type KeyLookup

type KeyLookup func(keyID string) ed25519.PublicKey

KeyLookup returns the public key for a key id when the file carries none, or nil. Which keys to trust is the caller's decision, never the file's: a key embedded in the file only identifies the signer.

type Level

type Level struct {
	Preset     *string
	CheckerPly *int
	CubePly    *int
	Rollout    *Rollout
	NoRollout  bool
}

Level is the nested level record (6.4), at one tier.

func (*Level) Effective

func (l *Level) Effective(parent *Level) *Level

Effective resolves a level field by field against the tier above (6.4).

type Match

type Match struct {
	Length              int // 0 = money session
	Variant             Variant
	WhiteName           string
	BlackName           string
	Rules               Rules
	CubeLimit           int // 0 = no limit
	ScoreStart          [2]int
	ScoreFinal          *[2]int // White, Black
	Result              int     // 9.3; 0 = absent (unfinished, or decided by the score)
	Source              *int    // 9.4
	StartedAt           *time.Time
	CompletedAt         *time.Time
	PlayerSeat          *Seat
	CrawfordBeforeStart bool
	Event               string
	EventYear           int
	DatePrecision       *int // 9.27
	Stage               string
	Round               int
	Table               string
	City                string
	Country             string
	EventURL            string
	Site                string
	MatchRef            string
	WhiteProfile        *Player
	BlackProfile        *Player
	Rated               bool
}

Match is the MTCH record (4).

type Player

type Player struct {
	UserID       string
	Rating       *float64 // hundredths decoded: 1523.47
	RatingSystem string
	Country      string
	Kind         *int // 9.28
}

Player is the nested player record (4.1).

type Ply

type Ply struct {
	Ref    int // ply_ref (I2)
	Action int // the real action id, escape resolved
	Seat   Seat
	Steps  []Step

	ExtraDice   *[2]int
	ResignValue *int
	CubeValue   *int
	Illegal     bool
	SettleValue *float64
	Board       *Board
	CubeOwner   *Seat
}

Ply is one ply record (5.1), with its extras decoded.

func (Ply) Dice

func (p Ply) Dice() [2]int

Dice returns the roll of a dice action, smaller die first.

func (Ply) IsDiceAction

func (p Ply) IsDiceAction() bool

IsDiceAction reports whether the ply is a dice roll (actions 0-20).

type PlyState

type PlyState struct {
	Before, After ogid.OGID
	// UnplayedRoll marks a dice action with no step followed by a
	// resignation or forfeit (5.4.5): not a checker decision.
	UnplayedRoll bool
}

PlyState is one ply's reconstructed position, before and after it, as the OGIDs the reference implementation derives (ogid_before, ogid_after).

type Probabilities

type Probabilities struct {
	Win, GammonWin, BackgammonWin, GammonLoss, BackgammonLoss float64
}

Probabilities are cumulative, from the decision owner's view (3.3).

type ResignDecision

type ResignDecision struct {
	CorrectValue    *int
	ResignError     *float64
	TakeResignError *float64
	Probs           *Probabilities
	EquityLoss      *float64
	Level           *Level
	ProducerRef     *int
}

ResignDecision is a RESIGN record (7.5).

type RollDecision

type RollDecision struct {
	Luck        float64
	Level       *Level
	ProducerRef *int
}

RollDecision is a ROLL record (7.6).

type Rollout

type Rollout struct {
	Trials            *int
	TruncationDepth   *int
	MovePly           *int
	VarianceReduction *int
	Seed              *uint64
	BudgetMS          *int
	MatchPolicy       *int
}

Rollout is the nested rollout record (6.2).

type Rules

type Rules uint64

Rules is the rules bitfield (9.2).

const (
	RuleCrawford Rules = 1 << iota
	RuleJacoby
	RuleBeavers
	RuleRaccoons
	RuleAutoDoubles
)

type Seat

type Seat int

Seat is the Seat enum (9.5).

const (
	White   Seat = 0
	Black   Seat = 1
	Centred Seat = 2
)

func (Seat) Opponent

func (s Seat) Opponent() Seat

Opponent returns the other seat of White or Black.

func (Seat) String

func (s Seat) String() string

type Shape

type Shape int

Shape is the file shape of 2.5.

const (
	ShapeMatch Shape = iota
	ShapeAnalysis
	ShapeCombined
)

type Signature

type Signature struct {
	Algorithm int // 9.16: 1 = Ed25519
	Signature []byte
	KeyID     string
	PublicKey []byte
	SignedAt  *time.Time
	Covers    uint64 // MSIG only, 9.26
}

Signature is a SIGN (8.1) or MSIG (8.6) record.

type SignatureReport

type SignatureReport struct {
	// Analysis is the index of the analysis block a SIGN belongs to, or -1
	// for an MSIG.
	Analysis int
	KeyID    string
	Status   SignatureStatus
}

SignatureReport is the verdict on one SIGN or MSIG.

type SignatureStatus

type SignatureStatus int

SignatureStatus is a verifier's verdict on one signature (8.1).

const (
	// Unverifiable: unknown algorithm or covered section, or no public key.
	Unverifiable SignatureStatus = iota
	// Valid: the signature matches the payload under the key.
	Valid
	// Invalid: the signature does not match the payload.
	Invalid
)

func (SignatureStatus) String

func (s SignatureStatus) String() string

type Step

type Step struct {
	From int // Point, 0-25
	Pips int // 1-6
}

Step moves one checker (5.1).

type Variant

type Variant int

Variant is the Variant enum (9.1).

const (
	Backgammon  Variant = 0
	Nackgammon  Variant = 1
	Hypergammon Variant = 2
	Longgammon  Variant = 3
)

func (Variant) Checkers

func (v Variant) Checkers() int

Checkers returns the variant's checkers per side (M1).

type Verdict

type Verdict int

Verdict is the cube verdict enum (9.13).

const (
	VerdictNoDouble Verdict = 0
	VerdictDouble   Verdict = 1
	VerdictTake     Verdict = 2
	VerdictPass     Verdict = 3
	VerdictTooGood  Verdict = 4
	VerdictBeaver   Verdict = 5
	VerdictRaccoon  Verdict = 6
)

Directories

Path Synopsis
Package ogid reads and writes OGID, the OpenGammon position ID.
Package ogid reads and writes OGID, the OpenGammon position ID.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL