πŸ€„MJPF

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:

FieldTypeReq.Meaning
formatstringyesMagic discriminator. MUST be "mjpf".
specstringyesVersion MAJOR.MINOR.PATCH (semver).
kindstringyesdeal, resume, result, or tournament-bundle.
requiresstring[]yesCapabilities a reader MUST understand. MAY be empty.
usesstring[]yesAdvisory capabilities. Unknown entries MAY be ignored.
gameobjectyesThe deterministic game reference (below).
eventsarraynoCanonical event log; only with the event-log capability.
resultobjectnoA played outcome.
metaobjectnoDescriptive metadata; never needed to reconstruct play.
extobjectnoVendor-extension namespace.

A reader MUST ignore any top-level field it does not recognize.

1.1 game β€” the deterministic reference

FieldTypeMeaning
seedstring64-bit RNG seed as a decimal string (see Β§5.2).
rulesetstringsimplified Β· hongkong Β· riichi-lite Β· mcr Β· taiwanese16
tierstringbasic Β· moderate Β· veryGood Β· wizard
modestringtraining Β· play
seatsintSeats at the table (4 for the standard game).
humanSeatintThe seat the importer plays.
recordSeatintSeat whose decisions drive the shared past. Equals humanSeat unless a branch.
branchPointIndexintEvent 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-logCarries a canonical move-by-move event log.
branchUses the branch / take-over-a-seat schedule.
resultCarries a played result.
bonus-tilesDeal/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)

  1. If format β‰  "mjpf" β†’ refuse (not MJPF).
  2. If the MAJOR of spec β‰  the reader's supported MAJOR β†’ refuse (incompatible major). Differing MINOR/PATCH within the same MAJOR are accepted.
  3. If any requires entry is unknown β†’ refuse (unsupported capability), naming the unknown flags.
  4. Otherwise proceed; ignore unknown uses, unknown top-level fields, and unknown ext keys.

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.

ChannelAvailability
QR code (MJQR)Cross-platform β€” the portable default.
Copyable codeCross-platform β€” universal paste-anywhere fallback.
AirDrop / Share sheetApple-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.xdoc 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

How to implement β†’ Schema & examples β†’