.arkdelta · MIME type: application/json · Encoding: UTF-8
The .arkdelta format is a JSON-based edit delta format for Arkadia MUD maps. It records a sequence of editing operations performed in ArkMap Studio on top of a known base map version, so the same set of changes can be re-applied later onto a newer upstream map release.
The typical workflow: a user downloads a map, makes local fixes in edit mode, and saves them as a .arkdelta file ("overlay" / kalka). When a fresh map version is released, the user downloads it, loads the overlay, and the changes are re-applied onto the new base.
Key design goals:
d:N) and receive fresh numbers on apply, like an overlay.arkmap canonical form of the map that was loaded when the edits were made (including the reduced-information variant derived from a Mudlet .dat file, where base identity is the file checksum of that derived .arkmap form). The checksum algorithm is the one defined by the .arkmap spec (currently v4 — XXH3-64, 16-hex); deltas written against older map checksum generations (CRC-32 8-hex, or v3 16-hex from ArkMap Studio v1.44.x) never match re-saved maps, so the base gate always reports a mismatch for them (informational dialog — the user may continue; see §4 and §11).{
"format": "arkdelta", // REQUIRED — magic string, must be exactly "arkdelta"
"format_version": 3, // REQUIRED — integer, format version (currently 3)
"meta": { ... }, // REQUIRED — base identity, provenance, bookkeeping (see §3)
"ops": [ ... ], // REQUIRED — ordered array of operation objects (see §7)
"checksums": { ... } // REQUIRED — XXH3-64 checksums, optional signature (see §5, §9)
}
format, format_version and checksums live at the top level in both map formats (.arkmap and .arkdelta). The format_version counters are independent — each format bumps its own counter on its own breaking changes.meta) are refused by conforming readers as an unsupported format version. There is no migration path — re-create the overlay from the edit log.Keys at every level are serialized in sorted order (see §5). The file is produced by buildDelta() from the editor's full edit log and consumed by validateDeltaText() + applyDelta().
meta, which is open, unknown top-level keys were historically covered by no checksum, so they cannot pass silently.)| Field | Type | Req | Description |
|---|---|---|---|
ops_count | integer | REQ | Number of operations; must equal ops.length |
base | object | REQ | Base identity (see §4); may be empty if the base could not be identified |
app_version | string | REQ | ArkMap Studio version that produced the file (informational) |
author | string | OPT | Author nickname, chosen by the user (for humans). Canonical form: lowercase, characters a-z and 0-9 only (1–32 characters) — case is insignificant everywhere, including key derivation. Added in format version 3. See the signature rules in §9 — author fields require a signature. |
author_id | string | OPT | Author fingerprint (for machines): the first 16 lowercase hex characters of SHA-256 over the raw bytes of author_pubkey. Added in format version 3. |
author_pubkey | string | OPT | Author's Ed25519 public key, 64 lowercase hex characters (32 bytes). Covered by checksums.file, so it cannot be swapped without invalidating the file checksum. Added in format version 3. |
created | string | OPT | Creation timestamp, ISO 8601. Informational. Added in format version 3. |
The magic string and format version moved to the top-level envelope in format version 3 (see §2); they no longer appear inside meta.
meta is an open object — readers MUST ignore unknown meta keys (forward compatibility). Operations, in contrast, are strict (see §10).meta.base identifies the map variant the delta was created against:
| Field | Type | Req | Description |
|---|---|---|---|
crc | string | REQ | File checksum (hex) of the canonical .arkmap form of the base map — the hierarchical file checksum as defined by the .arkmap spec §15 (algorithm v4: XXH3-64, 16 lowercase hex characters) |
version | string | OPT | Map version, copied from meta.user_data.version when present |
revision | string | OPT | Source revision (commit SHA), copied from meta.user_data.revision when present |
areas | object | OPT | Per-area base checksums: map of area ID (string key) → the v4 area checksum (16 lowercase hex) of that area in the base map, as defined by the .arkmap spec §15. Added in format version 3; recorded at map load time together with crc. |
base.areas enables per-area risk classification when reviewing an overlay against a different base: an operation touching an area whose checksum still matches the base is green (applies onto unchanged ground), an area whose checksum differs is yellow (the area moved under the overlay — review advised), and an area absent from the current map (or a delta without base.areas) falls back to the binary whole-file comparison (red / unknown). Absent base.areas, behavior is exactly the binary base match of older files.
The base checksum is computed once, at map load time — not at export time. The v4 engine computes it read-only (no cloning: the canonical encoding is independent of key order and in-memory ordering, and excludes the internal room.area field), reusing the checksums already computed during load verification — one computation per load.
On load of a delta, the application compares base.crc with the identity of the currently loaded map and informs the user whether the base matches ("baza zgodna") or differs ("kalka z innej wersji mapy"). A mismatch is not an error — applying onto a newer upstream version is the primary use case.
The file is serialized with the same canonical JSON writer as .arkmap (stableStringify): object keys sorted lexicographically at every level, no insignificant whitespace beyond the fixed pretty-print layout, UTF-8 encoding. The full canonical rules are those of the .arkmap specification (§16) — in particular, arrays of primitives with at most 8 elements are written inline on a single line (e.g. [255, 0, 0]), arrays of objects use the standard multi-line layout, and undefined values are omitted.
Checksums use XXH3-64 (seed 0; 16 lowercase hex characters) over the UTF-8 bytes of the canonical serialization — the same hash family as the .arkmap checksum engine (format_version 3; format_version 2 used XXH3-64 as well, format_version 1 used CRC-32, 8 hex):
| Field | Type | Description |
|---|---|---|
checksums.file | string | XXH3-64 of the canonical form of { format, format_version, meta, ops } — the whole file minus the checksums object itself. Because the checksums object is by definition outside this input, extra keys inside it (e.g. sig, §9) never affect the checksum and require no exclusion rules. |
checksums.ops | array<string> | Per-operation XXH3-64 of the canonical form of each op, in order; used to localize corruption |
checksums.sig | string | Optional Ed25519 signature over the file checksum — see §9. Readers unaware of signatures ignore it. |
Unicode normalization: the checksum input is the raw UTF-8 byte stream of the canonical serialization — verifiers MUST NOT normalize before hashing. Producers SHOULD emit strings in Unicode Normalization Form C (NFC) so that semantically identical text hashes identically across tools.
checksums.file does not match, the file is refused. The per-op checksums are then used to name the offending operations precisely (e.g. „op #17, #23 nie zgadza się z sumą kontrolną"). The per-op array is diagnostic only — it is not itself an integrity gate.Objects created by the delta (rooms, areas, labels) do not exist in the base map, so they have no meaningful numeric IDs. In the file they are identified by symbolic IDs:
sid := "d:" N // N ≥ 1, decimal, no leading zeros — regex: /^d:[1-9][0-9]*$/
Rules:
d:1, d:2, …). The file stays readable and sids never collide across kinds.ADD_ROOM / ADD_AREA / ADD_LABEL operation and may be referenced only by later operations. This holds by construction (the editor cannot reference an object that does not exist yet) and is enforced by validation.exits, special_exits, exit targets, source rooms, snapshots), a sid-created ID is written as its sid. Label sids follow the same rule inside their parent area; area sids likewise.On apply, each sid receives a fresh numeric ID from a monotonically increasing counter starting at max(existing IDs)+1 (per area for labels), and every reference is translated through the sid map.
Each entry in ops:
{
"seq": 1, // REQUIRED — 1-based, contiguous, in application order
"type": "ADD_ROOM", // REQUIRED — one of the 25 operation types below
"target": { ... }, // REQUIRED — object identity the op applies to
"payload": { ... }, // REQUIRED — operation data
"label": "..." // REQUIRED (may be "") — human-readable description, as in undo history
}
Room objects embedded in payloads (e.g. ADD_ROOM.payload.room) follow the .arkmap omission convention: empty containers and default values are omitted, and the internal area field never appears.
| type | target | payload | Notes |
|---|---|---|---|
ADD_ROOM | roomId (sid), areaId | room — full room object (id = sid) | Fresh numeric ID on apply |
DELETE_ROOM | roomId, areaId | room — snapshot at deletion time (informational) | Incoming exits are recomputed from live state on apply |
EDIT_ROOM | roomId | before, after — full room snapshots | Full-state replace, same semantics as undo entries |
EDIT_EXIT | roomId | before, after — full room snapshots | As EDIT_ROOM; distinct type for history labels |
PAINT_BATCH | — (empty) | changes: array of { roomId, beforeEnv, beforeSymbol, afterEnv, afterSymbol } | Batch recolor; rooms missing on apply are skipped individually |
MOVE_ROOM | roomId | fromX, fromY, fromZ, toX, toY, toZ | Coordinates in map cells |
MOVE_ROOM_TO_AREA | roomId | fromAreaId, toAreaId |
| type | target | payload | Notes |
|---|---|---|---|
ADD_EXIT | sourceId, dir | targetId, bidirectional | Refused by the editor guard when dir is occupied on apply |
DELETE_EXIT | roomId, dir | exitId (optional, informational) | |
DELETE_SPECIAL_EXIT | roomId | cmd, targetId (optional) | Also removes attached custom line / lock / door / weight |
| type | target | payload | Notes |
|---|---|---|---|
ADD_AREA | areaId (sid) | area — area object without rooms/labels | Fresh numeric ID on apply |
DELETE_AREA | areaId | name (informational) | Rooms and cross-area exits are cleaned from live state on apply |
EDIT_AREA | areaId | name, user_data, beforeName, beforeUserData |
| type | target | payload | Notes |
|---|---|---|---|
ADD_CL | roomId, dir | cl — custom line object | |
EDIT_CL | roomId, dir | before, after | |
DELETE_CL | roomId, dir | cl (optional, informational) | |
ADD_SUPPRESSOR | roomId, dir | — (empty) | Suppressor = empty red custom line |
DELETE_SUPPRESSOR | roomId, dir | cl (optional, informational) | |
AUTO_FIX_SUPPRESSORS | — (empty) | added: array of { roomId, dir, cl }, removed: array of { roomId, dir } | Batch form; entries whose room is missing on apply are dropped |
| type | target | payload | Notes |
|---|---|---|---|
ADD_LABEL | areaId | label — full label object (id = sid) | Fresh per-area ID on apply |
EDIT_LABEL | areaId, labelId | before, after | |
DELETE_LABEL | areaId, labelId | label — snapshot (informational) | |
MOVE_LABEL | areaId, labelId | fromX, fromY, toX, toY | |
RESIZE_LABEL | areaId, labelId | fromX, fromY, fromW, fromH, toX, toY, toW, toH |
| type | target | payload | Notes |
|---|---|---|---|
EDIT_ENV_COLOR | envId | oldColor, newColor — RGB arrays or null |
Producers MUST apply deterministic log compaction before serialization. The same edit log always produces the same file. Compaction rules (per object identity, order-preserving):
EDIT_ROOM / EDIT_EXIT operations on the same room collapses to one operation: the first before, the last after; if the collapsed before equals after, the operation vanishes.ADD_ROOM / ADD_AREA / ADD_LABEL followed by edits or moves of the same sid folds into a single ADD with the final state; likewise ADD_CL + EDIT_CL.MOVE_ROOM operations keeps the first from* and the last to*.EDIT_ENV_COLOR operations per envId collapse to the first oldColor and the last newColor.PAINT_BATCH entries per roomId collapse to the first before and the last after; batches may be merged.target/payload; the chain must be contiguous in the dependency sense, not necessarily in seq adjacency).After compaction, seq values are renumbered contiguously (validation already requires this), and the merged operation takes the label of its last component.
Compaction is a producer-side rule: validators MUST NOT reject uncompacted files. Two deliberate consequences, stated for the record: applied overlays enter the undo history at the compacted granularity, and intermediate-state guard refusals that would have occurred in the uncompacted log simply do not occur.
meta.created — a timestamp; signature material when signing is enabled) legitimately differ between two builds of the same log and are excluded from any byte-identity comparison.An overlay exists in exactly one of two states: anonymous (no author fields, no signature) or signed (author fields plus a valid signature). There is no third state — see the anomaly rule below.
The signature is an Ed25519 signature over the UTF-8 byte string "arkdelta-v3:" + canonicalSerialization(whole object minus checksums.sig) — the domain prefix followed by the canonical form (§5) of the entire envelope, with only the sig key excluded from checksums. It is stored as checksums.sig — 128 lowercase hex characters (64 bytes). The payload therefore binds the content, the metadata (including the author fields), every checksum entry, and any unknown top-level keys a lenient reader might otherwise keep. The canonical serialization is the same deterministic, key-sorted form used for the file itself, so the payload is well-defined regardless of byte-level key order in the file. Storing the signature inside checksums keeps it outside every checksum input by construction — no circularity, and unsigned files are completely unaffected.
The signer's public key is stored in meta.author_pubkey (Ed25519, 32 bytes, lowercase hex). Because meta is covered by checksums.file, the key cannot be swapped without invalidating the file checksum. The fingerprint meta.author_id is the first 16 lowercase hex characters of SHA-256 over the raw public-key bytes. How the key pair is generated or stored is implementation-defined (ArkMap Studio derives it deterministically via PBKDF2-SHA256 from a user-held recovery code of 3–6 words — default 3 — from a frozen 2048-word list, salted with the canonical nick — see the user manual); the format defines only the field semantics.
A producer that writes any author field (author, author_id, author_pubkey) MUST sign the file (write checksums.sig). Author fields without a signature are an anomaly: a reader reports them as a declaration without proof and treats the authorship as unverified.
checksums.file — unchanged, with or without a signature. A bad signature never substitutes for a checksum mismatch and never rescues one.checksums.sig is present, readers that support signatures SHOULD verify it against meta.author_pubkey. An invalid signature over an intact checksum is a loud warning, not a refusal: the file loads, authorship is treated as unverified.checksums and meta are ignored.format_version.Signers MAY register their nick in the public ArkMap identity registry, binding the canonical nick to their public key. The registry data lives in a public GitHub repository (Isithunzi000/arkadia-arkmap-identity-registry), one JSON document per nick at entries/<nick>.json, readable by any client over plain HTTPS (no authentication). Writes are only accepted through an unauthenticated-but-proof-gated gateway (https://arkmap-identity-registry.vercel.app/api):
POST /api/register with { nick, pubkey, author_id, sig }, where sig is an Ed25519 proof of possession over "arkmap-registry-v1:register:" + nick + ":" + pubkey. The server validates the canonical nick form (^[a-z0-9]{1,32}$, lowercase only), checks author_id against the key, and verifies the proof before writing. Registration is idempotent: re-registering the same nick with the same key succeeds; a different key is refused (nick_taken).POST /api/revoke with { nick, sig }, where sig is an Ed25519 signature over "arkmap-registry-v1:revoke:" + nick, verified against the key stored in the registry (never against a key supplied with the request). Revocation writes a permanent tombstone: revoked: true, revoked_at, revoke_sig, revoked_by: "owner". A revoked nick can never be registered again (410 nick_revoked) — this is deliberate: it makes revocation ungameable (no revoke-then-reclaim-by-attacker window).A registry entry records version, nick, author_id, pubkey, registered_at, register_sig, and the revocation fields above. The format of entries is versioned by version (currently 1). Public reads are served through a CDN cache, so a fresh registration or revocation becomes visible to readers within a few minutes (eventual, not instantaneous, consistency); readers MAY cache entries per session.
Registry checks are never a load gate: they refine how authorship is reported, and an unreachable registry (offline, blocked, down) must leave the reader fully functional — local signature verification still runs and the registry dimension is reported as unavailable. For a file carrying author fields with a signature that verifies locally, a registry-aware reader reports one of:
pubkey equals meta.author_pubkey: nick ownership confirmed (report as verified).For a declaration without proof (author fields without a signature), a registry mismatch or revoked result escalates the report from "unproven" to "conflicting with the registry — treat as false"; a match leaves it unproven (anyone can type a registered nick into an unsigned file).
Validation is fail-closed: any violation refuses the whole file with a list of diagnostics; nothing is loaded. Checks run in this order:
format must be "arkdelta".format_version must equal 3 (an unknown or older version — including 1 and 2 — is refused loudly and advises updating ArkMap Studio).format, format_version, meta, ops, checksums at the top level are refused.meta and every operation are scanned before any checksum computation; a structure nested deeper than 60 levels is refused. (Checksum computation serializes recursively — the guard turns a potential stack overflow into a controlled, precise refusal.)checksums.file must match the canonical form of { format, format_version, meta, ops }; on mismatch, per-op checksums localize the corrupted operations by seq.meta.ops_count must equal ops.length.seq values must be 1-based and contiguous. Uncompacted logs are legal — compaction (§8) is a producer-side rule only.type must be one of the 25 known types; target/payload must be objects carrying the required fields per type (§7). Operations are strict: unknown keys inside an operation are refused. (meta, in contrast, is open — unknown meta keys are ignored.)target.dir must be a valid direction (n, ne, e, se, s, sw, w, nw, up, down, in, out).__proto__, constructor, prototype are forbidden anywhere in an operation.checksums.sig are reported as a declaration without proof; an invalid checksums.sig over an intact checksums.file is a loud warning, and the file still loads (see §9).Applying a delta replays the operations in seq order onto the currently loaded map:
deleteRoom, commitDeleteArea, commitMoveRoomToArea, commitAddExit, commitMoveRoom, commitDeleteExit), the apply path calls it, so guard refusals behave exactly as in interactive editing (e.g. ADD_EXIT onto an occupied direction, or DELETE_AREA targeting the default area — areaId ≤ 0 is always skipped with a reason, the default area cannot be deleted). Remaining types are executed by reconstructing the equivalent undo entry and dispatching it through the same redo machinery used by undo/redo replay.{ applied, appliedSeqs, skipped: [{ seq, reason }] }, where appliedSeqs lists the seq numbers actually applied — the review panel uses them to mark the layer „naniesione z tej kalki” (applied from this overlay)..arkdelta again right after applying produces an updated overlay against the new base (rebase = re-save).ADD_ROOM/MOVE_ROOM onto an occupied cell) with a replacement position chosen by deterministic autopositioning or manual placement. Such overrides are an apply-time, per-session decision: they never enter the file format. They are re-validated against the live map immediately before commit (an occupied replacement cell skips the op with a reason, no data is overwritten), and the effective coordinates land in the edit log — so a subsequent rebase exports an overlay that already carries the corrected positions.format_version is bumped only on incompatible changes to the envelope or operation schemas. The counter is independent of the .arkmap counter (each format bumps its own).format_version (fail-closed) rather than guessing.meta is extensible — unknown informational keys in meta MUST be ignored by readers (and preserved by tools that round-trip the file). Operation target/payload objects remain strict — an unknown key or unknown operation type refuses the file (fail-closed). This asymmetry is deliberate: meta is annotation, ops is executable.format_version; older readers ignore them.A delta with three operations: a new area, a new room inside it, and an exit from an existing room (100) into the new room. (Whitespace abridged; real files use the canonical writer.)
{
"checksums": {
"file": "ae01c59324adaedf",
"ops": [ "37d3379e985f615d", "262f00d7717c507b", "037fd1e6593fac81" ]
},
"format": "arkdelta",
"format_version": 3,
"meta": {
"app_version": "v1.51.0",
"base": { "areas": { "1": "aaaabbbbccccdddd" }, "crc": "3f8a21c0b7d4e5f6", "revision": "0123456789abcdef0123456789abcdef01234567", "version": "0.205.0" },
"ops_count": 3
},
"ops": [
{ "label": "Dodanie obszaru \"Nowy obszar\"",
"payload": { "area": { "id": "d:1", "name": "Nowy obszar" } },
"seq": 1, "type": "ADD_AREA",
"target": { "areaId": "d:1" } },
{ "label": "Dodanie pokoju \"Polana\"",
"payload": { "room": { "env": 262, "id": "d:2", "name": "Polana", "x": 12, "y": -4, "z": 0 } },
"seq": 2, "type": "ADD_ROOM",
"target": { "areaId": "d:1", "roomId": "d:2" } },
{ "label": "Dodanie wyjścia #100 n",
"payload": { "bidirectional": true, "targetId": "d:2" },
"seq": 3, "type": "ADD_EXIT",
"target": { "dir": "n", "sourceId": 100 } }
]
}
A signed variant of the same overlay would additionally carry author, author_id, author_pubkey and created in meta, and sig in checksums (see §9). The checksum values above are the real hashes of the example content under the v3 checksum rule (§5), pinned by the test suite (tests/g6_crosscheck.js) so the example can never drift from the implementation.
On apply onto a map whose highest room ID is 6400 and highest area ID is 120: the area becomes 121, the room becomes 6401, and room 100 gains exit n → 6401 (with the reverse s → 100 on the new room, because bidirectional).
| Version | Date | Changes |
|---|---|---|
| 1 | 2026-08 | Initial format — ArkMap Studio v1.6.0 |
| 2 | 2026-08 | Checksums migrated from CRC-32 (8 hex) to XXH3-64 (16 hex) over the same canonical serialization — unified with the .arkmap checksum engine; base identity follows .arkmap alg v4 (ArkMap Studio v1.45.0). Version 1 files are refused loudly (no migration path — re-create the delta). |
| 3 | 2026-08 | Breaking (ArkMap Studio v1.51.0): unified envelope with .arkmap — format and format_version moved from meta to the top level; checksums.file now covers { format, format_version, meta, ops }. New: per-area base checksums meta.base.areas (§4) with green/yellow/red risk classification; normative producer-side log compaction (§8); optional provenance fields author/author_id/author_pubkey/created (§3) with optional Ed25519 signatures checksums.sig (§9) — author fields require a signature; validation documents the nesting-depth guard (§10) and the open-meta / strict-ops asymmetry (§12); Unicode hashing pinned (raw UTF-8 bytes, producers SHOULD emit NFC). Version 2 files are refused as an unsupported format version — no migration path (re-create the overlay). |
| 3 | 2026-08 | Additive / normative refinement (ArkMap Studio v1.52.0): signature payload redefined as "arkdelta-v3:" + the canonical serialization of the whole object minus checksums.sig (was: the file checksum alone). Signatures written by v1.51.0 do not verify under the new payload and are reported as invalid — re-save the overlay to re-sign (no legacy verification path). Top-level key whitelist added to validation (§10); recovery code word count 3–6 (default 3) with canonical nick (lowercase a-z/0-9) feeding the key-derivation salt; public identity registry with proof-of-possession registration and permanent owner revocation (§9 "Public registry"). |