MJPF Specification
Version 1.3.0 Β· Stable Β· file extension .mjpf Β·
proposed media type application/vnd.mahjong.mjpf+json
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, MAY are interpreted as in RFC 2119.
This page summarizes the normative spec; the canonical source is MJPF_SPEC.md in the project repo,
and the reference implementation is MJPF.swift.
1. Document model
An MJPF document is a JSON object. Top-level fields:
| Field | Type | Req. | Meaning |
|---|---|---|---|
format | string | yes | Magic discriminator. MUST be "mjpf". |
spec | string | yes | Version MAJOR.MINOR.PATCH (semver). |
kind | string | yes | deal, resume, result, or tournament-bundle. |
requires | string[] | yes | Capabilities a reader MUST understand. MAY be empty. |
uses | string[] | yes | Advisory capabilities. Unknown entries MAY be ignored. |
game | object | yes | The deterministic game reference (below). |
events | array | no | Canonical event log; only with the event-log capability. |
result | object | no | A played outcome. |
meta | object | no | Descriptive metadata; never needed to reconstruct play. |
ext | object | no | Vendor-extension namespace. |
A reader MUST ignore any top-level field it does not recognize.
1.1 game β the deterministic reference
| Field | Type | Meaning |
|---|---|---|
seed | string | 64-bit RNG seed as a decimal string (see Β§5.2). |
ruleset | string | simplified Β· hongkong Β· riichi-lite Β· mcr Β· taiwanese16 |
tier | string | basic Β· moderate Β· veryGood Β· wizard |
mode | string | training Β· play |
seats | int | Seats at the table (4 for the standard game). |
humanSeat | int | The seat the importer plays. |
recordSeat | int | Seat whose decisions drive the shared past. Equals humanSeat unless a branch. |
branchPointIndex | int | Event index where control switches seats (0 if not a branch). |
2. Document kinds
deal β only game (optionally a result as a score-to-beat). No
event log. The fully platform-neutral interop surface; a reader reconstructs a fresh seeded game.
resume β carries the events log; rebuilds the exact paused position. MUST
declare requires: ["event-log"] (plus "branch" for a take-over-a-seat branch).
result β carries a result for a deal. MUST declare requires: ["result"].
tournament-bundle (1.2.0) β carries a tournament object: one tournament seed
plus rule set, format and tier, from which every match's seed (and then every hand's seed inside each match)
is derived. One small document reproduces a whole multi-match sequence. MUST declare
requires: ["tournament-bundle"], and a tournament payload MUST NOT appear on any
other kind. The capability is REQUIRED on purpose: the document also carries a game ref, so a
reader that ignored the flag could open it as a single deal β which looks like a successful import while
silently discarding every match after the first.
3. Capabilities
Capabilities are how MJPF evolves without breaking old readers. A document lists what it requires (a reader MUST understand all of them) and what it merely uses (advisory; safe to ignore).
| Flag (since) | Meaning |
|---|---|
event-log | Carries a canonical move-by-move event log. |
branch | Uses the branch / take-over-a-seat schedule. |
result | Carries a played result. |
bonus-tiles | Deal/rule set uses flowers & seasons. |
view-only (1.1.0) | Spectator playback only β no resuming, forking or analysis. |
event-vocab-2 (1.1.0) | The event log may carry the 1.1.0 vocabulary additions. |
tournament-bundle (1.2.0) | The document is a whole tournament β a deterministic stack of deals. |
3.1 Reader algorithm (normative)
- If
formatβ"mjpf"β refuse (not MJPF). - If the MAJOR of
specβ the reader's supported MAJOR β refuse (incompatible major). Differing MINOR/PATCH within the same MAJOR are accepted. - If any
requiresentry is unknown β refuse (unsupported capability), naming the unknown flags. - Otherwise proceed; ignore unknown
uses, unknown top-level fields, and unknownextkeys.
3.2 Vendor extensions (ext)
A flat object for app-specific data. Keys SHOULD be vendor.key-namespaced; values are strings.
Readers MUST ignore keys they don't own and MUST NOT let ext affect game reconstruction.
4. MJQR transport
MJQR = base64( rawDEFLATE( UTF-8( canonicalJSON(document) ) ) )
canonical JSON = sorted keys, unescaped slashes, no insignificant whitespace. A v1 reader treats β€ 2200 UTF-8 bytes as single-QR-safe; larger documents fall back to a copyable text code.
| Channel | Availability |
|---|---|
| QR code (MJQR) | Cross-platform β the portable default. |
| Copyable code | Cross-platform β universal paste-anywhere fallback. |
| AirDrop / Share sheet | Apple-only conveniences carrying the same payload; never required. |
5. Platform-neutral constraints
5.1 β Compression is raw DEFLATE (RFC 1951), not zlib-wrapped. The stream has
no 2-byte zlib header and no Adler-32 trailer. Non-Apple readers MUST use raw
inflate (e.g. zlib.decompress(data, -15), inflateRawSync,
DecompressionStream('deflate-raw')). This is the #1 reason a naΓ―ve port fails β see
Implement.
5.2 β 64-bit seed is a decimal string, because JSON/JavaScript numbers are only exact to 253.
5.3 β JSON details: UTF-8; field order not significant; no NaN/Infinity; no comments.
5.4 β Determinism contract: two implementations produce identical play from the same deal iff they share the wall-construction algorithm and rule-set semantics. MJPF carries the seed and the rule-set id, not the rules. The reference wall algorithm is documented under Implement β Determinism.
6. Compatibility matrix
| Document β / Reader β | doc 1.0.x | doc 1.<newer minor> | doc 2.x |
|---|---|---|---|
| reader 1.0.x | β full | β
accepted; unknown fields & advisory caps ignored; refused only if it requires an unknown capability |
β refused |
| reader 2.x | β if 2.x stays back-compatible | β | β |
7. Versioning policy
- PATCH β editorial, no wire change.
- MINOR β additive only: new optional fields / capability flags. Old readers keep working.
- MAJOR β breaking change. Readers refuse a higher major. Last resort.