.arkmap Format Specification

Version 2.0 · ArkMap Studio · Isithunzi · 2026
File extension: .arkmap · MIME type: application/json · Encoding: UTF-8
Table of Contents
  1. Overview
  2. Top-Level Structure
  3. meta Object
  4. colors Object
  5. areas Array
  6. Room Object
  7. Exits & Directions
  8. Doors
  9. Special Exits
  10. Custom Lines
  11. Labels
  12. Font Object
  13. Color Type (RGB / RGBA)
  14. user_data
  15. Checksums (XXH3-64)
  16. Deterministic Serialization
  17. Validation Rules
  18. Rendering Behavior
  19. Mudlet .dat Round-Trip
  20. Extensions (.arkmap-only fields)
  21. Transport Lines (transports)
  22. Complete Example
  23. Optional Signatures
  24. Format Changelog

1. Overview

The .arkmap format is a JSON-based file format for storing MUD map data. It is designed as the canonical format for ArkMap Studio, a web-based map viewer and editor for the Arkadia MUD game world.

Key design goals:

Ground truth: The .arkmap format is derived from Mudlet's internal data model (TRoom, TArea, TMap classes in C++). When in doubt, Mudlet source code is the authoritative reference for field semantics.

2. Top-Level Structure

{
  "format":         "arkmap",      // REQUIRED — magic string, must be exactly "arkmap"
  "format_version": 2,              // REQUIRED — integer, format version (currently 2)
  "meta":          { ... },         // REQUIRED — metadata object (see §3)
  "colors":        { ... },         // REQUIRED — color palette object (see §4)
  "areas":         [ ... ],         // REQUIRED — array of Area objects (see §5)
  "checksums":     { ... },         // optional — XXH3-64 checksums, added by ArkMap Studio on save (see §15)
  "transports":    { ... }          // optional — transport lines, arkmap-transports v1 (see §21)
}
FieldTypeRequiredDescription
formatstringYESMust be "arkmap". Used to identify file type.
format_versionintegerYESFormat version. Currently 2. Increment on breaking changes. Renamed from version (format version 1) — see §24.
metaobjectYESMap-level metadata. See §3.
colorsobjectYESEnvironment color definitions. See §4.
areasarrayYESArray of Area objects. See §5.
checksumsobjectoptionalHierarchical XXH3-64 checksums. Moved from meta.checksums (format version 1) to the top level in format version 2. See §15.
transportsobjectoptionalTransport lines (ships, coaches, portals) — a universal, MUD-agnostic arkmap-transports v1 document. Not preserved in .dat export. See §21.
The envelope keys format, format_version, and checksums live at the top level in both formats (.arkmap and .arkdelta), so a generic dispatcher can identify file type and version without knowing the per-format meta layout. The format_version counters of .arkmap and .arkdelta are independent sequences; equal numbers carry no cross-format meaning.
Breaking change (format version 2): files written in format version 1 (key version instead of format_version, checksums under meta.checksums) are refused by conforming readers as an unsupported format version.

3. meta Object

{
  "map_name":                 "Arkadia",
  "symbol_font":              { /* Font object */ },
  "symbol_font_fudge_factor": 1.0,
  "use_only_map_font":        false,
  "user_data":                { /* optional string→string map (see §14) */ },
  "app_version":              "v1.51.0",  /* optional, informational — writer's version */
  "accepted_dir_issues":     [ /* optional, .arkmap-only — direction-validator acceptances */ ]
}
FieldTypeRequiredDescription
map_namestringYESDisplay name of the map (e.g. "Arkadia")
symbol_fontFontYESFont used for rendering room symbols on the map
symbol_font_fudge_factornumberYESMudlet's font size scaling factor (typically 1.0)
use_only_map_fontbooleanYESIf true, only use symbol_font; false = allow fallback
room_id_hashobject (string → integer)optionalMudlet's mRoomIdHash: contributor name → starting room ID, used by multi-contributor map merging (crowdmap). Preserved through .dat round-trips. Omit if empty. Added in spec v1.1.
user_dataUserDataoptionalMap-level key-value metadata. May contain version, revision, map_sync_version etc.
app_versionstringoptionalVersion of ArkMap Studio that wrote the file. Informational; ignored by validation. Mirrors the .arkdelta bookkeeping field. Covered by checksums.meta (see §15). Added in spec v2.0.
authorstringoptionalAuthor nickname (for humans). Canonical form: lowercase, characters a-z and 0-9 only (1–32 characters) — case is insignificant everywhere, including key derivation. Requires a signature — see §23. Additive extension: does not change the format version.
author_idstringoptionalAuthor fingerprint (for machines): the first 16 lowercase hex characters of SHA-256 over the raw bytes of author_pubkey.
author_pubkeystringoptionalAuthor's Ed25519 public key, 64 lowercase hex characters (32 bytes). Covered by checksums.meta and by the signature, so it cannot be swapped without invalidating both.
createdstringoptionalCreation timestamp, ISO 8601. Informational — self-declared by the producer.
accepted_dir_issuesarray of stringsoptional.arkmap-only — Acceptances for the direction validator (intentional team_follow_link / dir_bind mismatches marked as OK). Each entry is a key string "type:roomId:value", parsed by splitting on the first two colons (the value may itself contain :). The key embeds the numeric room ID, so it becomes stale when room IDs are renumbered (e.g. crowdmap merge) — acceptable, because acceptances are soft UX state. Written only when the validator's acceptance store is set to "file". Serialized sorted lexicographically (see §16). Not preserved in .dat export. See §14.

4. colors Object

{
  "env_colors":        { "258": 34, "272": 248 },
  "custom_env_colors": { "258": [0, 179, 0], "301": [255, 114, 14] }
}
FieldTypeRequiredDescription
env_colorsobjectoptionalMap of envId (string key) → ANSI palette index (integer 0–255). Lower priority than custom_env_colors.
custom_env_colorsobjectoptionalMap of envId (string key) → Color array. These override env_colors and the ANSI palette.

Color resolution order

  1. custom_env_colors[envId] — direct RGB, highest priority
  2. env_colors[envId] → ANSI 256-color palette lookup (explicit override)
  3. Hardcoded Arkadia environment table (ARKADIA_ENVS, 51 entries covering all known Arkadia envIds) — overrides implicit ANSI for Arkadia-specific envIds (200+). Applied only when the loaded map is detected as an Arkadia map (map-level user_data.map_sync_version present, "arkadia" in the map/file name, or ≥2 signature envIds >255 in use); maps from other MUDs skip this layer entirely and render from the ANSI palette plus their own env_colors / custom_env_colors — matching the official Mudlet renderer behavior.
  4. Implicit ANSI mapping: envId 1–255 → ANSI palette (lowest priority). Mudlet offset applies: envId 8 → ANSI index 0, envId 16 → ANSI index 8, all others → ANSI index = envId.

If none of the four layers provides a color for a given envId, the room renders with the Mudlet default color (rgb(114,1,0)).

ANSI 256-color palette

The env_colors mapping and the implicit ANSI layer resolve indices to RGB using the standard xterm-256 palette:

Mudlet offset: Mudlet shifts ANSI indices for the first system colors: envId 8 maps to ANSI index 0 (black), envId 16 maps to ANSI index 8 (bright black). All other envIds 1–255 map directly to their ANSI index. This matches Mudlet's internal color_table assignment in map-exporter.lua.

5. areas Array

An ordered array of Area objects. Must be sorted by id ascending for deterministic serialization.

{
  "id":             7,
  "name":           "Miasto",
  "rooms":          [ /* Room objects */ ],
  "labels":         [ /* Label objects — optional */ ],
  "grid_mode":      false,           // optional, default false
  "is_zone":        false,           // optional, default false
  "zone_area_ref":  0,               // optional, default 0
  "pos":            [0, 0, 0],       // optional, area position on overview
  "user_data":      { ... }           // optional
}
FieldTypeRequiredDescription
idintegerYESUnique area ID. Can be negative (Mudlet's Default Area is -1).
namestringYESDisplay name of the area.
roomsarrayYESArray of Room objects. Sorted by id ascending.
labelsarrayoptionalArray of Label objects. Sorted by id ascending.
grid_modebooleanoptionalMudlet grid mode flag. Default: false. Omit if false.
is_zonebooleanoptionalZone flag. Default: false. Omit if false.
zone_area_refintegeroptionalReference to parent zone area. Omit if 0.
pos[number, number, number]optionalArea position on Mudlet's overview map [x, y, z]. Omit if [0,0,0].
user_dataUserDataoptionalArea-level metadata.

6. Room Object

{
  "id":                   12847,
  "x":                    15,
  "y":                    -3,
  "z":                    0,
  "env":                  272,
  "name":                 "Skrzyżowanie dróg",
  "weight":               1,
  "symbol":               "K",
  "locked":               false,
  "hash":                 "abc123def456",
  "exits":                { "n": 12840, "e": 12848 },
  "stubs":                ["sw"],
  "doors":                { "n": "closed" },
  "exit_weights":         { "e": 3 },
  "exit_locks":           ["n"],
  "special_exits":        { "wejdz do jaskini": 15001 },
  "special_exit_locks":   ["wejdz do jaskini"],
  "custom_lines":         { /* see §10 */ },
  "user_data":            { "zone": "handlowa" },
  "notes":                "Tu jest kowal",
  "tags":                 ["shop", "blacksmith"]
}
FieldTypeRequiredDescription
idintegerYESGlobally unique room ID (across all areas).
xintegerYESX coordinate (east is positive).
yintegerYESY coordinate (north/up is positive — note: Mudlet negates Y for display).
zintegerYESZ coordinate (level/floor).
envintegerYESEnvironment ID. Determines room color via the color resolution chain (§4).
namestringoptionalRoom name. Omit if empty string.
weightinteger ≥ 1optionalPathfinding cost. Default: 1. Omit if 1.
symbolstringoptionalRoom symbol displayed on map (e.g. "P", "K", "[]"). Omit if empty. No length limit (Mudlet has none). ArkMap Studio fits the symbol font to the room square; symbols too long to stay legible are not drawn. Short symbols (1–2 chars) are recommended for readability.
lockedbooleanoptionalRoom locked flag (Mudlet isLocked). Locked rooms are skipped by pathfinding (not traversed through, but can be a destination). Omit if false.
hashstringoptionalMudlet room database hash (mpRoomDbHashToRoomId). Omit if not present.
hiddenbooleanoptionalHidden room — not drawn and skipped by pathfinding. In .dat (v20) there is no native field, so it round-trips through userData as system.hidden = "1" (readers accept both the flag and the userData key). Omit if false.
exitsobjectoptionalStandard exits. See §7.
stubsarray of stringsoptionalStub directions — exits without a known target. Sorted alphabetically.
doorsobjectoptionalDoor data. See §8.
exit_weightsobjectoptionalPer-exit pathfinding weights. See §7.
exit_locksarray of stringsoptionalLocked exit directions. Sorted alphabetically.
special_exitsobjectoptionalSpecial exits. See §9.
special_exit_locksarray of stringsoptionalLocked special exit commands. Sorted alphabetically.
custom_linesobjectoptionalCustom drawn lines. See §10.
user_dataUserDataoptionalRoom-level key-value metadata.
notesstringoptional.arkmap-only — Free-form notes. Not preserved in .dat export. Omit if empty.
tagsarray of stringsoptional.arkmap-only — Tags/labels. Not preserved in .dat export. Omit if empty array.
Omission convention: Optional fields should be omitted (not set to null or default) when they carry no information. This keeps files compact and diffs clean. For example, omit "weight" entirely when the value is 1, rather than writing "weight": 1.

7. Exits & Directions

Valid direction strings

Exits use short direction strings as keys. The complete set of 12 valid directions:

ShortLong (Mudlet)IndexScreen vector [dx, dy]
nnorth1[0, -1]
nenortheast2[1, -1]
nwnorthwest3[-1, -1]
eeast4[1, 0]
wwest5[-1, 0]
ssouth6[0, 1]
sesoutheast7[1, 1]
swsouthwest8[-1, 1]
upup9[0, 0]
downdown10[0, 0]
inin11[0, 0]
outout12[0, 0]
Coordinate systems: the screen vectors above are in display coordinates — as rendered, north/up on screen = negative Y (the same convention the Arkadia web client uses). Stored coordinates are different: in .arkmap and Mudlet .dat files north = positive Y (see §6). The display layer negates Y; the data model never does.

exits object

Maps direction → target room ID. Keys are short direction strings; values are integers (room IDs). Only existing exits are listed.

{ "n": 12840, "e": 12848, "up": 12900 }

exit_weights object

Per-exit pathfinding cost overrides. Keys must be valid direction strings that exist in exits or special_exits. Values are integers ≥ 1. A weight of 1 is allowed and meaningful: in Mudlet's .dat an absent weight entry means weight 0, so explicit 1s are preserved across .dat round-trips (both from import and from the editor). In ArkMap Studio's own routing an absent weight falls back to the target room's weight.

{ "e": 3 }

stubs array

Directions where an exit is "hinted" but the target room is unknown. Sorted alphabetically. May coexist with entries in exits (Mudlet allows this).

["sw", "w"]

exit_locks array

Directions where the exit is programmatically locked (distinct from doors). Sorted alphabetically. Locked exits are ignored by the pathfinder.

8. Doors

Maps direction string → door type string.

{ "n": "closed", "e": "locked" }
ValueMudlet intDescription
"open"1Door exists but is open
"closed"2Door is closed (can be opened)
"locked"3Door is locked (requires key)
Door keys can reference directions not present in exits or stubs. Mudlet allows doors to exist independently of exits.

9. Special Exits

Maps command string → target room ID. The command is a text string used in the MUD client (e.g. "wejdz do jaskini").

{
  "special_exits": { "wejdz do jaskini": 15001, "przejdz most": 15042 },
  "special_exit_locks": ["wejdz do jaskini"]
}

special_exit_locks is an array of command strings that are locked. Each value must exist as a key in special_exits.

Mudlet encoding: In the .dat binary, special exits are stored as "0command" (unlocked) or "1command" (locked) strings mapped by target room ID. The .arkmap format decomposes this into separate special_exits and special_exit_locks fields for clarity.
Namespace collision: Special exit commands share the doors, exit_weights, and custom_lines key namespace with standard exits. Avoid using standard direction names (n, ne, e, se, s, sw, w, nw, up, down, in, out) as special exit commands when a normal exit exists in the same direction — this will cause metadata conflicts.

10. Custom Lines

Custom lines are hand-drawn polylines on the map that replace the default straight-line exit connections. They are keyed by direction (for standard exits) or command string (for special exits).

"custom_lines": {
  "e": {
    "points": [[15.5, -3.0], [16.0, -2.5], [16.5, -3.0]],
    "color":  [255, 0, 0],
    "style":  "dash",
    "arrow":  true
  },
  "w": {
    "points": [],
    "color":  [255, 0, 0]
  }
}
FieldTypeRequiredDescription
pointsarray of [number, number]YESWaypoints of the polyline. Each element must be a 2-element array [x, y]. Coordinates are in map units (same as room x/y). On .dat import coordinates are rounded to 4 decimal places (see §19) — a sub-pixel difference, invisible when rendered. Empty array ([]) = empty custom line (hides the default exit line).
colorColoroptionalLine color. Default: [255, 0, 0] (red).
stylestringoptionalLine style. Default: "solid". Omit if "solid".
arrowbooleanoptionalShow arrowhead at the end of the polyline. Rendered in both view and edit mode. Omit if false.

Rendering

Custom lines are rendered as polylines starting from the room center (room.x, room.y), then through each waypoint in order. A custom line with 1 waypoint draws a single segment from room center to that point. The points array contains only waypoints — the room center is implicit and not stored in points.

Valid line styles

ValueMudlet intDescription
"solid"1Continuous line (default)
"dash"2Dashed line
"dot"3Dotted line
"dash_dot"4Alternating dash-dot
"dash_dot_dot"5Alternating dash-dot-dot

Empty Custom Lines

Critical concept: A custom line with an empty points array ("points": []) is an empty custom line — it hides the default exit line. This is an intentional Mudlet design feature, not data corruption. Empty custom lines must be preserved through all conversions.

An empty custom line hides the default exit line that Mudlet would normally draw from this room in the given direction. It is needed when the opposite room has a custom line pointing here — without it, both lines would be drawn (a double line).

Example: Room A (id=100) has custom line e → Room B (id=101). Room B should have an empty custom line: "w": { "points": [], "color": [255,0,0] } to hide the default westward line.

Validation rule

Custom line keys must reference existing exits or special exits. The key must appear in either exits or special_exits of the same room.

Orphan style/arrow entries

Only custom line entries with a points array (including empty arrays, i.e. suppressors) are represented in .arkmap. Style/arrow/color entries for directions without a points entry — possible in .dat, where these are separate maps — are dropped on import (see §19).

11. Labels

Labels are text annotations or image overlays on the map, associated with an area.

{
  "id":          3,
  "x":           12.5,
  "y":           -4.2,
  "z":           0,
  "width":       8.0,
  "height":      1.5,
  "text":        "Wyspa Miłości",
  "fg_color":    [200, 200, 100],
  "bg_color":    [0, 0, 0, 50],
  "no_scaling":  false,
  "show_on_top": true,
  "pixmap":      null
}
FieldTypeRequiredDescription
idintegerYESLabel ID, unique within the area. The same ID may exist in different areas.
xnumberYESX position in map coordinates.
ynumberYESY position in map coordinates.
zintegerYESZ level.
widthnumberYESWidth in map units.
heightnumberYESHeight in map units.
textstringYESLabel text. May be empty for image-only labels.
fg_colorColorYESForeground (text) color.
bg_colorColorYESBackground color. Alpha channel supported for transparency.
no_scalingbooleanoptionalIf true, label size is fixed regardless of zoom. Omit if false.
show_on_topbooleanoptionalIf true, label renders above rooms. Omit if false.
pixmapstring | nulloptionalBase64-encoded image data (Mudlet QPixMap). Null or omit for text labels.

12. Font Object

Represents a Qt QFont, serialized as JSON. Used for meta.symbol_font.

{
  "family":        "Bitstream Vera Sans Mono",
  "point_size":    -1,
  "pixel_size":    10,
  "style_hint":    7,
  "weight":        50,
  "style_setting": false,
  "underline":     false,
  "strike_out":    false,
  "fixed_pitch":   true
}
FieldTypeRequiredDescription
familystringYESFont family name.
point_sizenumberYESPoint size (-1 if pixel_size is used).
pixel_sizenumberYESPixel size (-1 if point_size is used).
style_hintintegerYESQt QFont::StyleHint enum value.
weightintegerYESFont weight (50 = normal, 75 = bold).
style_settingbooleanYESStyle setting flag.
underlinebooleanYESUnderline flag.
strike_outbooleanYESStrikeout flag.
fixed_pitchbooleanYESFixed pitch flag.

Optional font fields (omit when at defaults)

FieldTypeDefaultDescription
stylestring""Font style string.
style_strategyinteger0Qt style strategy.
stretchinteger100Font stretch (100 = normal).
letter_spacingnumber0Letter spacing.
word_spacingnumber0Word spacing.
hinting_preferenceinteger0Qt hinting preference.
capitalinteger0Capitalization mode.
kerningbooleantrueKerning enabled. Only include if false.
overlinebooleanfalseOverline flag.
style_obliquebooleanfalseOblique style flag.
ignore_pitchbooleanfalseIgnore pitch flag.
letter_spacing_is_absolutebooleanfalseIf true, letter_spacing is in pixels (not percentage).

13. Color Type (RGB / RGBA)

Colors are represented as JSON arrays:

Use RGB (3 elements) when alpha is 255 (fully opaque). Use RGBA (4 elements) only when alpha < 255. This keeps files compact.

[0, 128, 0]          // RGB green, alpha implied 255
[255, 0, 0, 128]     // RGBA semi-transparent red

14. user_data

A user_data field is a flat JSON object mapping string keys to string values. No nested objects, no arrays, no numeric values.

{ "version": "3.15.0", "revision": "abc123def456" }

Available at three levels: meta.user_data (map-level), area.user_data (area-level), and room.user_data (room-level).

user_data is preserved through .dat round-trips — Mudlet stores it as QMap<QString, QString>.

14.1 Reserved keys

Although user_data is a generic string→string map, ArkMap Studio and the Arkadia toolchain assign meaning to a set of reserved keys. They remain ordinary string values — any conforming reader may ignore them — but editors that understand them should preserve their formats verbatim. Unless noted otherwise, these are room-level keys.

Arkadia game mechanics (room)

Edited through the Skrypty tab of the room panel. All survive .dat round-trips (stored as Mudlet user_data).

KeyValue formatDescription
gpsJSON array (as string)GPS entries — a JSON-encoded array of objects, each with gps_string_lines (array of strings), room_id (target room) and area_name. Any additional fields (e.g. within_room_ids, line_delta) are preserved verbatim.
bindcommands joined by #Room command bind, internal form (e.g. otworz drzwi#wejdz).
bind_printablecommands joined by ;Human-readable form of bind (e.g. otworz drzwi;wejdz). Kept in sync with bind — the latter is bind_printable with ; replaced by #.
drinkable"true"Marks the room as a watering hole (wodopój). Absent or empty = not drinkable.
gatestringGate command for the room (e.g. uderz w brame).
walk_pre_cmdstringAuto-traversal command issued before walking through the room.
walk_post_cmdstringAuto-traversal command issued after walking through the room.
dir_binddir=cmd pairs joined by &Direction → command binds (e.g. down=odsun prycze#d). Each pair splits on the first =; surrounding whitespace is trimmed. A pair with a direction but an empty command is reported by the direction validator as an empty bind.
team_follow_linklabel*dir pairs joined by #Team-follow links (e.g. ścieżka do lasku*e). Each pair splits on the first * (1:1 with the arkadia-web client — the label therefore cannot contain *). A direction that is not an existing exit of the room is reported by the direction validator as a mismatch (rozjazd).

ArkMap Studio rendering (room)

Set through the room panel's Podstawowe tab. Stored as user_data, so they round-trip through .dat as opaque strings that Mudlet itself ignores.

KeyValue formatDescription
room.ui_borderColorQt color stringCustom room outline color (the Obrys option). When present together with a thickness, the room is drawn with a colored border instead of the default outline.
room.ui_borderThickness"1"–"10"Custom room outline thickness in pixels. Clamped to the 1–10 range.
system.fallback_hidden"true"Marks the room as hidden. Rendering of hidden rooms follows the active "Ukryte pokoje" view mode (faded / dashed / shown / hidden).

Mudlet system (room)

KeyValue formatDescription
system.fallback_symbol_color#rrggbbPer-room symbol color fallback. Mudlet .dat versions < 21 have no native per-room symbol-color field, so the color is carried here. On v20 export the room's symbol color is written into this key; on import it is read back from it.

Source pass-through (room)

These keys are injected by the source map (Arkadia mapping scripts / Mudlet) and are not written or interpreted by ArkMap Studio. They are preserved verbatim across both .arkmap and .dat round-trips.

KeyValue formatDescription
internal_idstring (e.g. "i1fd75c220")Source-generated unique room identifier. Present on every room in Arkadia maps (distinct from the numeric room.id). Opaque to ArkMap Studio.
descriptionstring (may be multi-line)Free-form room description carried by the source map (e.g. shop price tables). Present on a subset of rooms. Newlines are stored escaped within the string value.

Map-level keys (meta.user_data)

These keys commonly appear in meta.user_data and are written/read by ArkMap Studio:

KeyDescription
versionMap release version string (e.g. "3.15.0").
revisionMap revision / commit hash.
map_sync_versionMudlet map-sync version marker.
Forward compatibility: readers must not reject unknown user_data keys — any key not listed above is preserved verbatim. The reserved keys above are conventions layered on top of the generic string→string contract; they never change the underlying type.

15. Checksums (XXH3-64)

ArkMap Studio computes hierarchical XXH3-64 checksums on save and stores them in the top-level checksums object (moved out of meta in format version 2 — see §2). Every hashed value is first serialized with the canonical binary encoding — a deterministic, JSON-independent byte format whose normative reference is tests/checksums/CANONICAL_V4.md in the project repository. Checksums are optional — a file without checksums is valid.

"checksums": {
  "alg":   "v4",
  "file":  "a1b2c3d4e5f60718",
  "meta":  "f0e1d2c3b4a59687",
  "areas": { "7": "e5f6a7b8c9d0e1f2", "12": "c9d0e1f2a3b4c5d6" },
  "rooms": { "12847": "1a2b3c4d5e6f7a8b", "12848": "5e6f7a8b9c0d1e2f" },
  "transports": { "hash": "…", "lines": { "Wyzima - Novigrad": "…" } },  /* optional — transport line sums (see §21) */
  "sig":   "3f4a…"  /* optional — author signature, 128 lowercase hex (see §23) */
}

Computation algorithm (v4)

  1. Room hash = XXH3-64 (seed 0) of the canonical binary encoding of the room, domain prefix "r4". Default values are omitted from the encoding (weight: 1, locked: false, hidden: false, empty strings and empty containers), internal fields (area) are excluded, and custom line defaults are normalized (style: "solid" → omit, arrow: false → omit). The upstream hash field (e.g. "45:28:0:Wyzima") is covered when present. This ensures hash stability regardless of in-memory representation.
  2. Area hash = XXH3-64 of "a4" + the canonical encoding of the area attributes (id, name, the optional grid_mode/is_zone/zone_area_ref/pos fields when present, labels sorted by id — with full label color channels including alpha, count-prefixed —, user_data with keys in UTF-8 byte order) + the rollup of raw 8-byte little-endian room hashes sorted by room ID. Because the area's own fields and its id are part of the input, two empty areas are distinguishable.
  3. File hash = XXH3-64 of "f4" + the canonical encoding of the map-level colors tables (env_colors, custom_env_colors; numeric keys in ascending numeric order) + the rollup of raw 8-byte little-endian area hashes sorted by area ID.
  4. Meta hash (checksums.meta, added in format version 2) = XXH3-64 (seed 0) of the canonical binary encoding of the entire meta object, domain prefix "m4" (see the Meta object encoding section of CANONICAL_V4.md — keys in UTF-8 byte order, recursive). The two roles are deliberately separate: checksums.file is the map's identity — it excludes meta by design, so metadata edits (notes, acceptances, book-keeping) never change it; checksums.meta is the metadata's integrity — it detects external edits to meta without disturbing the identity hash. Keeping meta integrity separate is also what allows .arkdelta base matching (§4 of the .arkdelta spec) to rely on checksums.file alone.

All hash values are stored as 16-character lowercase hexadecimal strings. The canonical encoding fixes every cross-platform ambiguity: integers are little-endian i32, floats are little-endian f64 with -0 normalized to +0 and every NaN (or non-number) normalized to the canonical quiet-NaN 7ff8000000000000, strings are length-prefixed UTF-8, and map keys follow a fixed domain order (direction keys in n,ne,e,se,s,sw,w,nw,up,down,in,out order; other keys in UTF-8 byte order; numeric keys in ascending numeric order).

Unicode normalization: strings are hashed as their raw UTF-8 bytes exactly as they appear in the file — 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.

Algorithm versioning (alg)

"v4" is the only algorithm ArkMap Studio writes (since v1.45.0) and the only one it verifies. The retired generations — legacy v1 (no alg field), "v2" ("a2:"/"f2:" prefixed rollups) and "v3" (canonical binary, v1.44.x) — are no longer produced or verified; the formulas are frozen history.

Verifiers MUST dispatch on alg: "v4" → v4 formulas; any other value (including absent) → report an algorithm mismatch (loud, never a silent skip — a silently skipped stale file would be indistinguishable from a file without checksums). Re-saving the file upgrades its checksums to v4.

Transport checksums (checksums.transports)

When the map embeds a transport document (map.transports), saving additionally signs it into checksums.transports = { hash, lines }: hash covers the whole document, lines holds one sum per line keyed by line name — so verification pinpoints exactly which line was modified (badLines), added unsigned (missingLines) or removed leaving an orphaned sum (extraLines). Unlike room/area/meta sums, transport sums use the canonical JSON encoding (deterministic stableStringify — keys sorted at every level, absent keys omitted), hashed as XXH3-64 of its UTF-8 bytes; the canonical document sorts lines by name (array order is not semantic) while preserving leg order (leg order is semantic). Transport integrity is an auxiliary, informational signal (same class as metaOk): it never affects file identity and is reported separately from map-data sums. Re-saving after removing transports clears the orphaned sums.

Verification

On load, recompute from bottom up — using the algorithm recorded in alg (see Algorithm versioning above) — and compare. Mismatches indicate the file was modified externally (hand-edited JSON, merge conflict resolution, etc.). Mismatches are warnings, not fatal errors. Verification never throws on malformed data (it reports a verification error and defers to structural validation), and it also reports dictionary inconsistencies: rooms or areas missing their checksum entry, and orphaned checksum entries without a matching object. Dictionary-inconsistency reporting applies to the areas and rooms dictionaries only; unrecognized top-level keys inside the checksums object itself (e.g. keys introduced by a newer spec) are ignored, not reported — verifiers must remain forward-compatible with the envelope.

Scope (v4): room hashes cover room data (including the upstream hash); area hashes additionally cover the area's own in-scope attributes (id, name, user_data, labels with full color channels, and the optional grid_mode, is_zone, zone_area_ref and pos fields — inside the scope since v4); the file hash additionally covers the colors tables; the meta hash covers the entire meta object. Transport line sums (checksums.transports) are signed and verified separately (canonical JSON encoding — see §21). The file key is still a rollup (hash of area hashes + colors), not a hash of the entire document byte stream; v4 dropped the redundant global room rollup (rooms are already rolled up through their areas).

16. Deterministic Serialization

For git-friendly output, .arkmap files use stable JSON serialization:

This serialization ensures that two independently generated .arkmap files representing the same data produce byte-identical output, which is critical for git-based workflows and checksum verification. The same canonical array ordering (areas, rooms, and labels by id; stubs and exit locks alphabetically) is also applied when exporting to Mudlet .dat, so two .dat exports of the same logical map are byte-identical regardless of how the in-memory map was edited.

17. Validation Rules

A conforming implementation should validate the following rules:

  1. format must be "arkmap"
  2. format_version must be 2
  3. meta, colors, areas must be present
  4. checksums, if present, must be an object; meta.checksums is not a recognized location (format version 1 layout — rejected by rule 2)
  5. Unknown keys anywhere in the document (top level, meta, areas, rooms, labels, colors) must not cause rejection — readers preserve them verbatim and writers re-emit them (see §1); only the rules in this list may reject or warn. Unknown top-level keys are additionally reported at load (an informational note listing them) and are covered by the optional signature (§23)
  6. meta.room_id_hash, if present, must be an object mapping contributor names (strings) to starting room IDs (integers)
  7. All area IDs must be unique
  8. All room IDs must be globally unique (across all areas)
  9. Exit target room IDs must reference existing rooms
  10. Special exit target room IDs must reference existing rooms and be integers
  11. Special exit command keys must be non-empty strings
  12. Special exit commands should not collide with standard direction names when a normal exit exists in the same direction (shared namespace for doors, exit_weights, custom_lines)
  13. Exit weight keys must reference existing exits or special exits
  14. Special exit lock commands must reference existing special exits
  15. Door values must be one of: "open", "closed", "locked"
  16. Custom line keys must reference existing exits or special exits
  17. Custom line style must be one of: "solid", "dash", "dot", "dash_dot", "dash_dot_dot"
  18. Custom line points must be arrays of [number, number] pairs (empty array = empty custom line)
  19. Color arrays must have 3 or 4 integer elements, each 0–255
  20. Room weight must be integer ≥ 1
  21. Room coordinates (x, y, z) must be integers
  22. Room name and symbol must be strings (if present)
  23. Room locked must be boolean (if present)
  24. user_data keys and values must be strings
  25. Label IDs must be unique within their area
  26. doors, exit_weights, custom_lines must be objects (not arrays) if present
  27. special_exit_locks must be an array if present
Permissive loading: Implementations should validate but still allow loading files with non-fatal warnings. Only a missing/wrong format or a missing/wrong format_version are fatal errors. There is no migration path and no backward compatibility: files written in format version 1 (with version: 1 and meta.checksums) are refused as an unsupported format version.

18. Rendering Behavior

This section documents rendering conventions that implementations should follow for visual compatibility with Mudlet and Delwing.

Exit rendering

Custom line rendering

Custom lines are drawn as polylines starting from the room center, then through each waypoint in order. Arrowheads (when arrow: true) are rendered in both view and edit modes. Custom lines with an empty points array (empty custom lines) are not drawn but may be indicated with a ⊘ symbol at the room edge.

Color fallback

Rooms with envIds not resolved by any color layer render with the Mudlet default color rgb(114, 1, 0) (dark brownish-red). This applies to all visual contexts: canvas, minimap, room panel swatch, env color dialog, and palette chips. The legend and palette chips refresh dynamically after map load to reflect any custom_env_colors overrides.

19. Mudlet .dat Round-Trip

The .arkmap format is designed for lossless conversion to/from Mudlet's binary map.dat format. ArkMap Studio reads versions 17–22 and always writes version 20 (the Arkadia MUD standard). Round-trip conversion .dat → .arkmap → .dat produces byte-identical output for version 20 input files that are already in the canonical Qt layout — in practice, files previously written by ArkMap Studio (verified with SHA-256). Files written by external tools (e.g. Mudlet itself or Arkadia mapping scripts) are preserved semantically but not byte-identically: their content is normalized on import as listed below. Files in other versions (17–19, 21–22) are read correctly but re-written as version 20, so the output will differ structurally.

Normalizations on .dat import

ArkMap Studio always writes .dat files in the canonical layout (see §16). When importing a .dat file produced by external tools, the following normalizations are applied. They do not change the meaning of the map:

Known limitations

None currently known — as of spec v1.1, all previously identified data-loss cases (mRoomIdHash, exit weights equal to 1, orphan custom line attributes) have been addressed; the normalizations listed above are the only intentional differences from the source file.

Direction mapping (.arkmap → .dat)

.arkmap uses short strings; .dat uses 1-based indices (see §7 table). Convert using the direction table.

Door mapping

.arkmap uses strings; .dat uses integers: open=1, closed=2, locked=3.

Custom line style mapping

.arkmap uses strings; .dat uses integers: solid=1, dash=2, dot=3, dash_dot=4, dash_dot_dot=5.

Special exits encoding

.dat stores special exits as QMap<int targetRoomId, QList<QString>> where each string is prefixed with "0" (unlocked) or "1" (locked). .arkmap decomposes this into special_exits + special_exit_locks.

Y-axis convention

Important: Mudlet negates Y for per-area bounds: min_y = -max(room.y), max_y = -min(room.y). The .arkmap format stores Y as-is (positive = north). Implementations must negate Y when computing area bounds for .dat export.

Color conversion

Mudlet uses QColor (spec, r, g, b, alpha, pad). .arkmap uses [r, g, b] or [r, g, b, a]. When alpha is 255, omit it. When converting to QColor, set spec=1 (RGB), pad=0.

Font conversion

QFont bit flags are decomposed into boolean fields in .arkmap. See §12 for the mapping.

20. Extensions (.arkmap-only fields)

These fields are exclusive to the .arkmap format and are not preserved when exporting to Mudlet .dat:

FieldLocationDescription
notesRoom objectFree-form text notes for a room.
tagsRoom objectArray of string tags for filtering/categorization.
checksumstop-level objectXXH3-64 integrity checksums (auto-generated by ArkMap Studio on save; top-level envelope key since format version 2 — see §15).
transportstop-level objectTransport lines document (arkmap-transports v1) — universal routing data, signed per line via checksums.transports. See §21.
app_versionmeta objectVersion of ArkMap Studio that wrote the file (informational).
accepted_dir_issuesmeta objectDirection-validator acceptances, written only when the validator's acceptance store is "file" (see §14).

Implementations that only handle .dat conversion can safely ignore these fields. When reading a .arkmap file for .dat export, these fields should be silently dropped.

21. Transport Lines (transports)

A map may embed a transport document describing named transport lines — ships, coaches, portals, anything that moves the player between non-adjacent rooms. The format is universal: it describes routing data for any map of any MUD; game-specific content is just data on top of the same schema. The document may also live as a standalone sidecar JSON (same schema) for tools that keep routing data out of the map file.

{
  "format":  "arkmap-transports",   // REQUIRED — magic string
  "version": 1,                    // REQUIRED — schema version
  "lines": [
    {
      "name":  "Wyzima - Novigrad ferry",  // REQUIRED, unique — lines are keyed by name
      "board": ["wsiadz na statek", "wejdz na statek"],  // REQUIRED — boarding commands (aliases)
      "exit":  "zejdz ze statku",          // REQUIRED — disembark command
      "legs": [                                // REQUIRED, ordered — the ride sequence
        { "from": 729, "to": 3760, "time": 23, "label": "Bialy Most" },
        { "from": 3760, "to": 729, "time": 18, "label": "Wyzima" }
      ]
    }
  ]
}
FieldTypeRequiredDescription
namestringYESUnique line name (duplicate names are a validation error — lines are keyed by name).
boardarray of stringsYESCommands that board the line; the first is canonical, the rest are aliases.
exitstringYESCommand that disembarks.
legs[].from / topositive integerYESRoom ids of the boarding / arrival stops.
legs[].timenumber (seconds)optionalMeasured travel time; when absent, routing cost assumes 60 s (TRANSPORT_DEFAULT_TIME).
legs[].labelstringoptionalHuman-readable stop name.

Semantics

22. Complete Example

A minimal but valid .arkmap file with one area containing two rooms:

{
  "areas": [
    {
      "id": 1,
      "name": "Test Area",
      "rooms": [
        {
          "env": 272,
          "exits": {
            "e": 2
          },
          "id": 1,
          "name": "Start Room",
          "x": 0,
          "y": 0,
          "z": 0
        },
        {
          "env": 258,
          "exits": {
            "w": 1
          },
          "id": 2,
          "name": "Forest",
          "symbol": "T",
          "x": 1,
          "y": 0,
          "z": 0
        }
      ]
    }
  ],
  "checksums": {
    "alg": "v4",
    "areas": { "1": "202da9297d3fa8c6" },
    "file": "5d03008d88f80d3c",
    "meta": "81b665ced34bce7a",
    "rooms": { "1": "8e006383da3c0aa8", "2": "fa11ba21960714e5" }
  },
  "colors": {
    "custom_env_colors": {
      "258": [0, 179, 0],
      "272": [128, 128, 128]
    }
  },
  "format": "arkmap",
  "format_version": 2,
  "meta": {
    "map_name": "Test Map",
    "symbol_font": {
      "family": "Bitstream Vera Sans Mono",
      "fixed_pitch": true,
      "pixel_size": 10,
      "point_size": -1,
      "strike_out": false,
      "style_hint": 7,
      "style_setting": false,
      "underline": false,
      "weight": 50
    },
    "symbol_font_fudge_factor": 1.0,
    "use_only_map_font": false
  }
}
Note that top-level keys are sorted alphabetically (areas, checksums, colors, format, format_version, meta). Within each room, keys are also sorted. This is the deterministic serialization format. The checksum values above are the real v4 hashes of the example content — cross-verified against two independent implementations (the ArkMap Studio encoder and the reference Python oracle) and pinned by the test suite (tests/g6_crosscheck.js), so the example can never drift from the specification text.

23. Optional Signatures

A map file 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 scheme mirrors the .arkdelta signature (§9 of the .arkdelta spec): the same field names, the same key type, the same payload construction — only the domain prefix differs (arkmap-v2: versus arkdelta-v3:).

What is signed

The signature is an Ed25519 signature over the UTF-8 byte string "arkmap-v2:" + canonicalSerialization(whole object minus checksums.sig) — the domain prefix followed by the deterministic serialization (§16) of the entire envelope, with only the sig key excluded from checksums. The payload therefore binds the content (areas/rooms), the colors tables, the metadata (including the author fields), every checksum entry, and any unknown top-level keys preserved under the forward-compatibility rule (§17). It is stored as checksums.sig — 128 lowercase hex characters (64 bytes). Storing the signature inside checksums keeps it outside every checksum input by construction — no circularity, and unsigned files are completely unaffected.

Producer rule

A producer that writes any author field (author, author_id, author_pubkey, created) MUST sign the file (write checksums.sig) and MUST recompute checksums.meta after writing the author fields, so the meta hash covers them. Author fields without a signature are an anomaly: readers report them as a declaration without proof and treat the authorship as unverified.

Reader policy

Public registry

Signers MAY register their nick in the public ArkMap identity registry — one nick bound to exactly one key, with owner-initiated permanent revocation (tombstone). The registry, its gateway, and the registry-aware reader policy (match / missing / mismatch / revoked / offline) are specified once in §9 of the .arkdelta spec ("Public registry") and apply unchanged to map signatures — the registry binds identities, not file formats. Registry checks are never a load gate; an unreachable registry only marks the registry dimension unavailable.

What a signature proves: continuity of identity — the same key holder produced the file; with the public registry (above) it can additionally be checked for nick ownership and revocation. It does not prove real-world identity (registration is uncurated; interfaces should always show nick and fingerprint, never the nick alone) and it does not vouch for content. Key generation and storage are implementation-defined (ArkMap Studio derives the key 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).

24. Format Changelog

VersionDateChanges
12026Initial release. Full Mudlet .dat v20 compatibility. Extensions: notes, tags, checksums.
12026Additive, non-breaking: documented reserved user_data keys (Arkadia mechanics, room UI, Mudlet system — §14); added optional meta.accepted_dir_issues (.arkmap-only, direction-validator acceptances).
12026Documentation only (no format change): documented source pass-through keys internal_id and description (§14.1); clarified checksum scope — room/area graph only, not whole-document (§15).
12026Documentation only (no format change): terminology update — "exit suppressor" renamed to "empty custom line"; validator label updated to "double line".
12026Additive normative (no format change): algorithm versioning — meta.checksums records "alg": "v2"; verifiers MUST dispatch on alg (absent = frozen legacy v1 formulas; unknown = skip silently) (§15).
12026Additive normative (no format change): checksum engine v3 — meta.checksums records "alg": "v3" (XXH3-64 over canonical binary encoding, 16-hex values); v1/v2 formulas retired — treated as unknown algorithms and skipped silently (§15).
12026Normative correction (ArkMap Studio v1.44.1): the v3 area encoding gained user_data (UTF-8 byte-ordered keys, key+value strings), restoring the v2 area scope. Files written by v1.44.0 (v3 without area user_data in scope) recompute under the corrected encoding — no v3 files existed outside the project mirror at the time of the correction (§15).
12026Additive normative (no format change; ArkMap Studio v1.45.0): checksum engine v4 — meta.checksums records "alg": "v4" (domain prefixes r4/a4/f4; label colors with all channels incl. alpha; area grid_mode/is_zone/zone_area_ref/pos in scope; room hash in scope; redundant global room rollup dropped). v3 retired — any non-v4 alg is a loudly reported mismatch, never a silent skip; verification never throws and reports missing/orphaned entries (§15).
22026Breaking (ArkMap Studio v1.51.0): unified envelope with .arkdelta — version renamed to format_version (now 2); checksums moved from meta.checksums to the top level; new checksums.meta entry (meta integrity, domain prefix m4 — checksums.file remains the meta-independent identity hash); optional meta.app_version; meta.accepted_dir_issues parsing pinned (split on first two colons) and serialization pinned (sorted lexicographically); Unicode hashing pinned (raw UTF-8 bytes, producers SHOULD emit NFC); preserve-unknown-keys rule stated normatively (§17). Version 1 files are refused as an unsupported format version — there is no migration path.
22026Additive (no format change; ArkMap Studio v1.52.0): optional author signatures — meta.author/author_id/author_pubkey/created and checksums.sig (Ed25519 over "arkmap-v2:" + the canonical serialization of the whole object minus checksums.sig), mirroring the .arkdelta §9 scheme; unknown top-level keys are reported at load (§17); nick canonical form pinned (lowercase a-z/0-9); public identity registry (nick bound to one key, owner-initiated permanent revocation) (§23).
22026Additive (no format change): optional top-level transports field — a universal, MUD-agnostic arkmap-transports v1 document (named transport lines with boarding/disembark commands and timed legs) — and its per-line integrity sums checksums.transports ({ hash, lines }, canonical JSON encoding, informational class like meta; §15, §21). Reference implementation: arkmap npm package (validateTransports / addTransportChecksums / verifyTransportChecksums / buildTransportEdges).

.arkmap Format Specification v2 · ArkMap Studio · Isithunzi · AGPL-3.0 License · 2026