BPSE Puzzle Format

BPSE (Binary Puzzle State Encoding) is the compact serialisation format Lazy Sudoku uses to encode a complete puzzle state — including the grid, candidates, given/solved status, user annotations, and game metadata — into a URL-safe string.

When you copy a puzzle using the BPSE format (the default in Copy Puzzle), the resulting string looks like this:

eNqbzDiFgZHhwJQjMUbMHYoNjlxSDlq6XubLeI9sUfO...

This page describes the format in detail, so you can write your own parser if you want to interoperate with Lazy Sudoku puzzle strings.

Encoding Pipeline

A BPSE string is produced in three steps:

Puzzle State → MessagePack → zlib deflate → base64url

To decode, reverse the pipeline:

base64url → zlib inflate → MessagePack → Puzzle State

base64url uses the URL-safe alphabet (RFC 4648 §5): A-Z, a-z, 0-9, -, _, with no padding characters.

Top-Level Structure

The MessagePack payload is an array of 3 elements:

Index Name Type Description
0 version uint8 Format version (currently 1)
1 header array(4) Game metadata
2 body array(4 or 5) Puzzle state data

Parsers should check the version first and reject unknown versions.

Header

The header is a fixed array of 4 elements:

Index Name Type Values
0 game_type uint8 0 = Classic 9×9, 1 = Jigsaw 9×9
1 game_profile uint8 0 = Relaxed, 1 = Standard, 2 = Deep
2 pencil_flags uint8 Bitfield (see below)
3 puzzle_id string or nil Puzzle identifier (e.g. "jade-drey")

Pencil Flags

Bit Mask Meaning
0 0x01 Paper mode enabled
1 0x02 Housekeeping (auto-eliminate from peers on placement)
2 0x04 Full paper mode (immersive stylus view)

Body (Classic 9×9)

The body is an array of 4 elements (or 5 in paper mode):

Index Name Type Size Description
0 candidates bin 92 bytes Engine candidate bitmap
1 givens bin 11 bytes Given-cell mask
2 solved bin 11 bytes Solved-cell mask
3 notes map variable Sparse note overrides
4 user_candidates bin 92 bytes User candidate bitmap (paper mode, only when divergent)

Candidates — bin(92)

A packed bitfield of 729 bits (81 cells × 9 digits), stored as 92 bytes in little-endian bit order.

Bit addressing: bit index cell × 9 + (digit - 1), where cell is 0–80 (row-major: cell = row × 9 + col) and digit is 1–9.

To extract byte and bit position for a given cell and digit:

bit_index = cell * 9 + (digit - 1)
byte_index = bit_index / 8
bit_offset = bit_index % 8
is_set = (bytes[byte_index] >> bit_offset) & 1

Givens — bin(11)

An 81-bit mask packed into 11 bytes (little-endian bit order). Bit i is set if cell i is an immutable given.

Solved — bin(11)

Same layout as givens. Bit i is set if cell i has been solved by the user (not a given).

Notes — map

A MessagePack map encoding only non-default note states. Keys are cell indices (uint8, 0–80). Values are inner maps where keys are digits (uint8, 1–9) and values are note states (uint8):

Value State
2 Excluded (crossed out by user or technique)
3 Highlighted (user-highlighted)

Cells with no overrides are omitted. An empty puzzle has an empty map ({}).

Example: cell 5 has digit 3 excluded and digit 7 highlighted:

{ 5: { 3: 2, 7: 3 } }

User Candidates — bin(92) (paper mode, divergent only)

Present only in paper mode (pencil_flags bit 0 set) and only when the user's candidates differ from the engine candidates. Same layout as the engine candidates bitmap, but represents what the user has actually pencilled in.

In paper mode, the engine bitmap and user notes can diverge — the engine tracks all valid candidates, while the user manages their own notes by hand. When the two are identical (e.g. the user has filled in all valid candidates), this field is omitted to save space. When absent, a parser should derive note states from the engine candidates bitmap.

Body (Jigsaw 9×9, future)

For game_type 1, the body will include a 5th element (or 6th in paper mode):

Index Name Type Size Description
4/5 box_assignments bin 81 bytes One byte per cell, box index 0–8

Size

A typical puzzle with 25 givens and auto-filled candidates produces a BPSE string of ~170–180 characters. With puzzle ID and note overrides, expect ~200–250 characters. Paper mode adds the user bitmap but zlib compresses the redundancy well.

For comparison, the SudokuWiki S9B format is 165 characters but carries less information (no game profile, pencil mode, puzzle ID, or excluded/highlighted annotations).

Detecting BPSE Strings

A BPSE string consists entirely of base64url characters (A-Za-z0-9-_), is typically 100–300 characters long, and does not start with S9B (which identifies the SudokuWiki format) or look like an 81-character digit string. Lazy Sudoku's parser tries BPSE decoding for any input that is all base64url characters and is not a valid grid string.

Reference Implementation

The canonical implementation is in the Lazy Sudoku source code: