npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

arkmap-js

v1.1.0

Published

Mudlet map toolkit for Node.js and the browser: read/write .dat and .arkmap maps, validate, XXH3-64 checksums, map diff, .arkdelta edit deltas (validate/build/apply, Ed25519 signature verification), routing, transports, waypoint route codes, token-indexed

Downloads

641

Readme

arkmap-js

arkmap-js is a toolkit for MUD map files in JavaScript — Node.js and the browser. It provides full support for Mudlet's native binary map format (.dat — read, write, convert both ways) and acts as a bridge to .arkmap — this project's own JSON map format for MUD games. .arkmap is not a Mudlet format; it keeps everything .dat carries and adds what .dat cannot express. On top of plain I/O the toolkit adds structural validation, canonical deterministic serialization, and content-integrity checksums (XXH3-64, v4).

Zero dependencies. ESM only. Node ≥ 18 and modern browsers.

npm install arkmap-js
import { loadArkmap, saveArkmap, datToArkmap, arkmapToDat, validate } from 'arkmap-js';

// .arkmap (text/JSON)
const { map, validation, checksums } = loadArkmap(fileText);
if (!validation.ok) console.error(validation.errors);

// Mudlet .dat (binary) — full pipelines
const mapFromDat = datToArkmap(arrayBuffer);   // .dat -> arkmap map object
const datBytes   = arkmapToDat(map);           // arkmap map object -> .dat Uint8Array

// write back .arkmap (canonical, deterministic, with checksums)
const text = saveArkmap(map);

Demo viewer

Try it now, no install: isithunzi000.github.io/arkmap-js — a zero-build viewer that runs entirely on this package, in your browser.

Drag & drop a .dat / .arkmap file (or pass ?src=<url>) and you get:

  • the map fitted to the window on open, with per-area / per-level navigation, a geographic minimap (click/drag pan) and a zoom bar with cursor-anchored wheel zoom and a live zoom ratio,
  • validation and checksum status — including per-line transport integrity,
  • room search with jump & highlight and a room info panel,
  • cross-area exits drawn as arrows colored by the target environment, with the target area name — double-click an arrow (or a room with a single cross-area exit) to jump straight to that area and room,
  • map labels from the file (styled text, sized and placed exactly like ArkMap Studio's default text-label mode), rendered under/above the rooms as stored,
  • multi-waypoint route planning on arkmap-js/graph (Dijkstra/A*, direction filters, transport modes) with a schematic route overview, fit-route and gen-3 arkmap: route codes (live export / paste import) via arkmap-js/waypoints,
  • true-vector SVG / PNG export of the current view via arkmap-js/render-svg, with a native save-as dialog (typed filename) where the browser supports it — arrows and map labels included.

The viewer's source is a single hand-written docs/index.html — a practical example of building a full map app on the package with zero dependencies.

The .arkmap format

.arkmap is our own JSON map format for MUD games (introduced by ArkMap Studio, specified openly): areas, rooms, exits, doors, labels, custom exit lines, environments, user data, and transport lines — everything the native Mudlet .dat carries, plus things .dat cannot express.

Why use it instead of raw .dat:

  • Integrity built in — every file can carry canonical XXH3-64 checksums (v4) over the whole file, every area, every room, and meta; tampering or corruption is detectable per-room, not just "file won't load".
  • Deterministic — canonical key ordering and serialization mean the same map always produces the same bytes: diffs, reviews, and version control work.
  • Text, not binary — inspectable in any editor, diffable in git, parseable by every language with a JSON parser.
  • Extensible — meta and user_data fields carry data the .dat format has no place for, without breaking anything.
  • Routing data on board — named transport lines (transports) with per-line integrity sums ride inside the map file; .dat cannot express them.
  • Faithful bridge — .dat → .arkmap conversion is lossless, so the binary world and the JSON world stay in sync.

Full format documentation (maintained alongside ArkMap Studio):

The toolkit

Repository layout

src/            library modules (constants, codecs, validation, checksums, converters, graph/routing,
                transports, waypoints, diff, edit deltas, search index, i18n, rendering)
scripts/        extract.mjs (module pipeline), run-tests.mjs, build-demo.mjs
tests/          node:test suites + golden fixtures and oracle vectors
docs/           demo viewer (GitHub Pages) + prebuilt browser bundle
EXTRACT_MANIFEST.json   module build manifest

Commands

| command | what it does | |---|---| | npm test | run the full node:test suite — round-trips, checksum oracle vectors, validation & i18n, graph/routing, transports, waypoints, diff, .arkdelta, rendering | | npm run parity | regenerate src/ in memory and fail on any drift from the manifest | | npm run extract | regenerate src/ modules per the manifest |

API

.arkmap I/O

| function | description | |---|---| | loadArkmap(text) | parse + validate; returns { map, validation, checksums }; throws on invalid JSON / non-map JSON | | saveArkmap(map) | canonical .arkmap text (deep-cloned, sorted, checksums refreshed); deterministic | | createEmptyMap(name?) | minimal map object that passes validate() | | stableStringify(value) | deterministic JSON (sorted keys) — the canonical form primitive |

Mudlet .dat I/O

| function | description | |---|---| | datToArkmap(arrayBuffer) | full pipeline: .dat bytes → arkmap map (throws on unsupported version) | | arkmapToDat(map) | full pipeline: arkmap map → .dat bytes (Uint8Array) | | readMudletDat(arrayBuffer) | low-level: raw Mudlet dat structure | | writeMudletDat(rawDat) | low-level: raw structure → bytes |

The .dat codec reads binary versions 17–22 and writes v20 — the version current Mudlet itself writes. Note that .dat is the poorer format: arkmap checksums and meta fields have no .dat counterpart. Converting .arkmap → .dat keeps everything .dat can express; .dat → .arkmap is lossless.

Validation & integrity

| function | description | |---|---| | validate(map, opts?) | structural validation → { ok, errors[], warnings[] }; errors carry { path, code, msg } — code is stable/machine-readable, msg follows opts.locale (see Internationalization) | | addChecksums(map) | compute and attach v4 checksums (in place) | | verifyChecksums(map) | verify → { present, ok, fileOk, metaOk, badAreas[], badRooms[], missingRooms[], missingAreas[], extraRooms[], extraAreas[] }; present: false means the map is unsigned (then ok: true); metaOk is informational only and never lowers ok | | checkSuppressorsInMap(map) | data-quality lint: missing custom-line suppressors |

Constants

FORMAT ('arkmap') · FORMAT_VERSION (2) · CHECKSUM_ALG ('v4') · MUDLET_DAT_READ_MAX (22) · MUDLET_DAT_WRITE_VERSION (20) · DIRS, DIR_BY_SHORT, DIR_BY_LONG, DIR_BY_IDX · DOOR_INT, DOOR_STR · LINE_INT, LINE_STR

Arkadia layer

Game-specific data for the Arkadia MUD is available under the arkmap-js/arkadia subpath — kept out of the universal root API:

import { ARKADIA_ENVS, ARKADIA_SYMBOLS, envPaletteList, isArkadiaMap } from 'arkmap-js/arkadia';

Graph & routing

Room graph over a map: indexing, adjacency, weighted routing (Dijkstra / A*), direction filters, locked exits, transport hops, multi-waypoint planning and room search. Available from the root and under the arkmap-js/graph subpath. Pure and stateless — same input, same output.

Edge weight semantics follow Mudlet: a positive exit_weights[dir] wins; otherwise the step costs max(targetRoom.weight, 1) (default 1).

| function | description | |---|---| | buildIndex(map) | room lookup: Map(roomId → { room, areaId, areaName }) (duplicate ids: last wins) | | edgeWeight(room, dir, targetRoom) | weight of one exit per the semantics above | | neighborsOf(room, idx?) | adjacency [[targetId, weight], …] — exits + special_exits (special wins on duplicate target); pass idx for full weight semantics | | findPath(fromId, toId, idx) | shortest path (Dijkstra, default weights) as [roomId, …]; [fromId] when start = end; null for unknown ids / unreachable | | findRoute(fromId, toId, idx, opts?) | full router → { path, hops }. opts: algorithm ('dijkstra' | 'astar'), dirMode ('all' | 'cardinal' | 'vertical' — cardinal = compass exits, vertical = compass + up/down), avoidLocked (default true), isLocked(room) override, transportMode ('off' | 'normal' | 'aggressive'), transports (a document), transportEdges (prebuilt), extraEdges (ad-hoc virtual edges). hops[i] is transport metadata for step i → i+1 or null for walking. Transports force Dijkstra (the A* heuristic is inadmissible over hops) | | planRoute(waypoints, idx, opts?) | route through consecutive waypoints → { legs: [{ from, to, path, hops } \| null], totalSteps, complete } | | countSpecialSteps(path, idx) | how many steps of a path use special_exits | | searchRooms(query, map, limit = 25) | digits or #id → exact id match; otherwise case-insensitive substring on room.name (region is the name suffix, so region search works); map order, cut at limit |

import { buildIndex, findRoute, searchRooms } from 'arkmap-js/graph';

const idx = buildIndex(map);
const hit = searchRooms('karczma', map)[0];
const { path, hops } = findRoute(currentRoomId, hit.room.id, idx, {
  algorithm: 'astar',
  dirMode: 'cardinal',
  transportMode: 'normal',
  transports: map.transports,          // optional: embedded transport lines
});

Transports

Transport lines (ships, coaches, portals — anything that moves you between non-adjacent rooms) use the universal arkmap-transports v1 format, valid for any map and any MUD. A document can be embedded in a map file as the top-level map.transports field or kept as a sidecar JSON:

{
  "format": "arkmap-transports",
  "version": 1,
  "lines": [
    {
      "name": "Wyzima - Novigrad ferry",   // unique — lines are keyed by name
      "board": ["wsiadz na statek"],        // boarding commands (aliases)
      "exit": "zejdz ze statku",            // disembark command
      "legs": [                             // ordered — the ride sequence
        { "from": 729, "to": 3760, "time": 23, "label": "Bialy Most" }
      ]
    }
  ]
}

Line order in the array is not semantic (canonical form sorts by name); leg order is semantic. time is in seconds; when omitted, costing assumes TRANSPORT_DEFAULT_TIME (60). Hop cost = Σ times · ratio + boarding penalty — one penalty per ride, so a direct crossing beats transfers (normal: 30 / 0.5, aggressive: 10 / 0.1).

Integrity. Like rooms and areas, transports can carry canonical XXH3-64 checksums — a whole-document hash plus one per line (map.checksums.transports = { hash, lines }), so verification pinpoints exactly which line was tampered with, added, or removed. Transport sums are reported separately from map-data sums (auxiliary routing data, same class as meta).

| function | description | |---|---| | validateTransports(doc) | schema validation → { ok, errors[] } with exact paths | | normalizeTransports(raw) | compact tuple format ([name, board[], exitCmd, [[from,to,time,label],…]], used by Arkadia community data) → standard document | | addTransportChecksums(map) | sign map.transports into map.checksums.transports (in place); re-signing after removal clears orphan sums | | verifyTransportChecksums(map) | verify → { present, ok, unsigned?, hashOk, badLines[], missingLines[], extraLines[] }; never throws | | buildTransportEdges(doc, idx, { mode }) | virtual edges for the router: Map(roomId → [{ to, cost, hop }]); chains stop at rooms missing from the map |

Available from the root and under the arkmap-js/transports subpath. Arkadia's own transport lines ship as data under arkmap-js/arkadia/transports:

import { ARKADIA_TRANSPORTS } from 'arkmap-js/arkadia/transports';
import { buildIndex, findRoute } from 'arkmap-js';

const idx = buildIndex(map);
const route = findRoute(a, b, idx, { transportMode: 'normal', transports: ARKADIA_TRANSPORTS });

Waypoint route codes

Waypoint lists travel between tools as compact text codes — arkmap:<algo><dir><trans>:<ids CSV>:<crc8> — carrying the routing options alongside the room ids:

arkmap:dwp:2188,1998,729:16990e69
       │││   │               └─ integrity crc (see below)
       │││   └─ waypoints: canonical CSV of room ids
       ││└─ transports: p = off, n = normal, g = aggressive
       │└── directions: k = cardinal, p = +vertical, w = all
       └─── algorithm: d = Dijkstra, a = A*

Universal (any map, any MUD), fully lowercase and case-insensitive on decode, fail-closed on corruption, hard-capped (WP_MAX = 200 waypoints, ROUTE_CODE_MAX = 64 000 chars). The trailing crc8 — first 8 hex chars of xxh3-64 over the lowercased arkmap:<flags>:<ids> core — catches accidental damage when a code is pasted around (typos, truncation, mangled characters); it is an integrity check, not a security feature. Older code generations (ARKMAP:/ARKMAP2:, base64 payloads) are rejected by design — no backward compatibility. Available from the root and under the arkmap-js/waypoints subpath.

| function | description | |---|---| | encodeRoute(waypoints, opts?) | [id, null, id, …] + { algorithm, dirMode, transportMode } → code string, or '' when unencodable (never produces a code the decoder would reject) | | decodeRoute(code, hasRoom?) | → { ids, valid, invalidCount, total, algorithm, dirMode, transportMode }; null on structural corruption; { error: 'crc', expected, actual } on checksum mismatch; { error: 'too-many', max, total } over the waypoint limit. hasRoom(id) (e.g. id => idx.has(id)) splits ids into valid / invalidCount |

import { encodeRoute, decodeRoute } from 'arkmap-js/waypoints';

const code = encodeRoute([729, 3760, 10313], { algorithm: 'astar', dirMode: 'all', transportMode: 'normal' });
// 'arkmap:awn:729,3760,10313:…' — paste-safe, shareable

const d = decodeRoute(code, id => idx.has(id));
if (d && !d.error) console.log(d.valid, d.algorithm, d.dirMode, d.transportMode);

Map diff

diffMaps(srcMap, dstMap) compares two maps and returns a list of edit operations that turn the first map into the second: what changed (ADD_ROOM, DELETE_EXIT, EDIT_LABEL, PAINT_BATCH, … — 21 op types covering areas, rooms, exits, moves, env colors, custom lines and labels), where (room/area ids), and the before/after state where reverting matters. The list is topologically ordered, so applying the ops top to bottom never breaks references (e.g. rooms are added before exits can point at them, areas are deleted last). Each op carries a human-readable label (English by default; pass { locale: 'pl' } for Polish labels byte-identical to ArkMap Studio's history panel — see Internationalization). Universal — works on any arkmap-shaped maps, any MUD. Available from the root and under the arkmap-js/diff subpath.

import { diffMaps } from 'arkmap-js/diff';

const { entries, stats, overlap, srcRooms, dstRooms } = diffMaps(oldMap, newMap);
// entries — ordered, deterministic ops; each carries a human-readable `label`
//           (English default; { locale: 'pl' } for Studio-pinned Polish)
// stats   — per-op-type counts
// overlap — room-id kinship ratio 0..1: a low value warns you are probably
//           diffing two unrelated maps

Semantics worth knowing:

  • Canonical comparison — the same map loaded from .dat and .arkmap produces an empty diff (defaults stripped, array fields sorted).
  • Cascading deletes — deleting a room also trims exits pointing at it, so no separate DELETE_EXIT ops appear for those.
  • Move cycles — swapping two rooms' positions breaks the collision cycle with one EDIT_ROOM fallback, then the rest resolves as MOVE_ROOM.
  • Deterministic — same input pair, byte-identical output.

Edit deltas (.arkdelta)

.arkdelta is the edit-delta format of ArkMap Studio: an ordered op log (25 op types) cut against a base map, with canonical XXH3-64 integrity checksums and optional Ed25519 author signatures (format specification — rendered · specification source). The package ships the reader (fail-closed validation, signature verification, base identity — arkmap-js/delta-validate), the writer (delta build, deterministic compaction, op serialization — arkmap-js/delta-build) and apply (pure in-place application with Studio semantics — arkmap-js/delta-apply). All three are also exported from the root.

| function | description | |---|---| | validateDeltaText(text, opts?) | parse + validate → { ok, errors[], codes[], delta? }; never throws; errors follow opts.locale, codes are stable machine names | | verifyDeltaSignature(delta) | async Ed25519 verification → { state: 'unsigned' \| 'claimed' \| 'ok' \| 'bad', ... }; never refuses the load | | computeBaseInfo(map, precomputed?) | base-map identity for meta.base comparison → { crc, version?, revision?, areas } | | deltaChecksums(meta, ops) | canonical integrity sums { file, ops[] } | | buildDelta(log, base, opts?) | op log (the shape diffMaps returns) → .arkdelta file text; sid d:N allocation, compaction, checksums | | serializeDeltaOps(ops, base, opts?) | ready ops → .arkdelta file text (fresh meta + checksums) | | DELTA_EXPORTABLE | the 25 op types a delta can carry | | applyDelta(map, delta, opts?) | applies a validated delta to a map in place → { applied, appliedSeqs, skipped[] }; the delta is never mutated; per-op isolation — a failing op is skipped with a reason (opts.locale, EN default, PL byte-pinned to Studio) and a stable code, the rest still applies; opts.onlySeq applies only the given seqs, opts.overrides (seq → { x, y }) re-validated fallback positions, opts.seedSids pre-resolved d:N ids |

Constants: ARKDELTA_FORMAT ('arkdelta') · ARKDELTA_FORMAT_VERSION (3) · ARKDELTA_MAX_OPS (5000) · ARKDELTA_MAX_BYTES (8 MiB).

import { validateDeltaText, verifyDeltaSignature } from 'arkmap-js/delta-validate';

const res = validateDeltaText(text);            // English messages
const resPl = validateDeltaText(text, { locale: 'pl' }); // Studio-pinned Polish
if (res.ok) {
  const sig = await verifyDeltaSignature(res.delta);
  console.log(sig.state); // 'unsigned' | 'claimed' | 'ok' | 'bad'
}

Validation is fail-closed: unknown top-level/op keys, bad checksums, out-of-sequence ops, unresolved d:N symbolic ids and prototype-polluting keys all refuse the load. Polish messages are byte-identical to ArkMap Studio's validator, so Studio can adopt this package with zero user-visible change (see Internationalization).

The writer is deterministic: same log and base, byte-identical file. Compaction (spec §8) folds redundant chains (edit→edit, add→edit, add→delete, paint merges) without changing the applied result. Op labels are copied from the log entries — produce them with diffMaps(a, b, { locale: 'pl' }) for a Polish delta. The round trip is closed: validateDeltaText(buildDelta(log, base)) always validates, and applyDelta executes a validated delta with Studio-exact op semantics (exit guards refuse without mutation, room/area deletes cascade, room.area backlinks are maintained like Studio's live model). Apply is deterministic: same map, delta and options → same result and same report.

Token-indexed room search

buildSearchIndex(map) precomputes a token index (lowercase word → room ids, separately for room-name and area-name hits); searchIndexed(index, query, limit = 25) answers queries without rescanning the map. Scoring is parity with ArkMap Studio's planner search: each query word found in the room name = 2 points, in the area name = 1 point, a room whose id equals the numeric query = 999; results sort by score desc with stable map-order tie-breaks, cut at limit. Universal — any arkmap-shaped map, any MUD. Available from the root and under the arkmap-js/search-index subpath.

import { buildSearchIndex, searchIndexed } from 'arkmap-js/search-index';

const idx = buildSearchIndex(map);            // build once, query many times
searchIndexed(idx, 'karczma smok');           // multi-word: intersect, cumulative score
// -> [{ roomId, name, areaName, score }, ...]

Vector rendering (SVG / PNG)

renderSvg(map, opts) renders a map to a true-vector SVG string — no raster inside. Scope filters (areaId, z), env colors (same resolution chain as the demo viewer), exits with undirected dedup, arrowheads colored by the target environment for exits leading to a known room outside the scope (plain stubs for unknown targets), optional room-name labels, optional map labels (mapLabels: true — styled text and pixmap images from area.labels, honouring show_on_top and the z filter), route overlays (walking segments solid, transport hops dashed) and waypoint markers. Fully deterministic: same input, byte-identical SVG. Available from the root and under the arkmap-js/render-svg subpath.

svgToPng(svg, { scale = 2 }) rasterizes such a self-contained SVG to a PNG Blob in the browser (Blob URL → <img> → canvas → toBlob; the canvas stays untainted because the SVG has no external references). Available from the root and under the arkmap-js/render-png subpath.

renderPng(map, opts) is the one-call convenience — renderSvg(map, opts) + svgToPng with the same options (everything above, arrows and map labels included) plus pngScale (default 2). PNG output therefore carries exactly what the SVG would. Same module, same browser-only rule.

import { renderSvg } from 'arkmap-js/render-svg';
import { svgToPng, renderPng } from 'arkmap-js/render-png';

const svg = renderSvg(map, {
  areaId: 'all', z: null,
  mapLabels: true,                                 // text + pixmap area labels
  routes: [{ path, hops }],                        // optional overlay
  markers: [{ id: 2188, color: '#60a5fa', label: '1' }],
});
const pngBlob = await svgToPng(svg, { scale: 2 }); // browser only
const samePng = await renderPng(map, { areaId: 'all', mapLabels: true, pngScale: 2 });

Studio display parity (render-model)

arkmap-js/render-model is the shared display engine behind the demo viewer and renderSvg: pure functions that turn map data into drawing ops in map units (screen Y is the negated data Y), parameterized by cell size in px. Rooms, env colors (full resolution chain: ANSI palette < Arkadia environments < env_colors < custom_env_colors), exits (widths, dashes, one-way arrowheads, doors at the drawn-line midpoint), stubs, custom lines (styles, doors, arrows), cross-area arrows, room symbols, special-exit markers, hidden rooms, stack shadows and the adaptive LOD tiers (full / roomsOnly / raster, with a budget-based raster fallback) all reproduce ArkMap Studio's non-edit display 1:1 — same classification, geometry, colors and zoom gates. Editor-only features (selection handles, edit markers beyond the default special-exit marker, drag previews) are intentionally out of scope.

The whole surface is pure and zoom-parameterized — pass cellPx (px per map unit, i.e. CELL × zoom) and get plain op data back in map units:

| group | API | |---|---| | color cache | buildColorCache(map) — env → RGB resolution chain (ANSI palette < Arkadia envs < env_colors < custom_env_colors); roomColorCss(cache, env) / roomColorRgb(cache, env) — room fill for an env id | | hidden rooms | isRoomHidden(room) · hiddenRoomStyle(room, hiddenMode) ('hide' | 'faded') | | symbol colors | contrastCss(cssColor) — readable foreground for a fill · symbolColorCss(room, cache) / symbolFillCss(room, cache) — glyph color / inner-triangle fill | | exit classification | classifyExit(room, dir, targetId, plane) → { kind: 'custom' \| 'suppressed' \| 'skip' \| 'udio' \| 'cross' \| 'crossZ' \| 'oneway' \| 'line', ... }, in Studio's drawExits precedence | | op builders | roomOp(room, cache, cellPx, hiddenMode?) · exitLineOp(room, target, vec, cellPx, oneWay) · stubOps(room, cellPx) · customLineOp(room, dir, cl, cellPx) · doorSquareOp(mx, my, doorState, cellPx) (non-string door states fall back to locked) · innerTrianglesOp(room, cache, cellPx) · symbolOp(room, cache, cellPx) · seMarkerOp(room, cache, cellPx) · stackShadowsOp(room, cellPx) — all return null when the Studio zoom gate closes the layer | | cross-area | crossArrowOp(room, vec, target, cache, cellPx) — env-colored arrow (unknown target → CROSS_UNKNOWN_CSS) · specialCrossArrows(room, plane, cache, cellPx) — arrows for special exits to known rooms in other areas · crossExitEntries(room, plane, cache, cellPx) — every cross-area exit (regular + special) with edge/anchor/direction; op is null when a custom line owns the direction, so area labels and dblclick hit-testing work even where no arrow is drawn | | helpers | edgePoint(cx, cy, half, svec) · lineWidthUnits(cellPx) · dashPattern(style, cellPx) · gridStyle(cellPx) | | LOD & raster | lodMode(planeCount, cellPx, W, H) → 'full' | 'roomsOnly' | 'raster' · rasterModel(planeRooms, byId, cache, hiddenMode?) → RGBA bytes, one cell per map unit, exit lines under room cells |

Constants: CELL (18 px per map unit at zoom 1) · ROOM_UNITS (0.65) / ROOM_HALF · DIR_VEC / OPP_DIR / UDIO_DIRS · LINE_CSS, ONE_WAY_FILL, CROSS_UNKNOWN_CSS, CL_DEFAULT_CSS, DOOR_CSS, DEFAULT_ROOM_RGB · HIDDEN_FADE (0.35) · LOD thresholds LOD_MIN_CELL_PX (9), LOD_ROOMS_BUDGET (200), LOD_RASTER_CELL_PX (3) · RASTER_LINE_ALPHA (140).

Parity is verified against the Studio original as an oracle: an op-stream diff over 1,008 scenarios (hand-written, a real-map slice and seeded fuzz) matches with zero differences, and a headless-Chromium pixel diff of the same map view shows the raster tier pixel-identical, roomsOnly at 0.05% and full mode at 1.17% of pixels — all of it 1px antialiasing fringe from transformed rasterization (structural delta <= 0.02%). On top of the oracle, the display engine survives a seeded property fuzz — thousands of random maps checked for save/load round-trip equality, checksum and validation consistency, renderer-op finiteness and SVG determinism — and a headless-Chromium pixel fuzz across seeded fixtures and zoom tiers, where the only differences are the same antialiasing fringe.

Testing & guarantees

The package is tested against real Arkadia MUD maps in both formats — .arkmap and .dat (~27,000 rooms): full round-trip conversions in both directions, checksum verification against an external oracle, deterministic-save guarantees and the graph suite (pathfinding/search properties on golden fixtures and synthetic edge cases) all run in CI on Node 18/20/22.

The .arkdelta engine (reader, writer, apply) is additionally verified against the ArkMap Studio original used as an oracle: byte-identical validation messages (EN/PL), byte-identical built deltas, byte-identical applied maps across hand-written scenarios covering all 25 op types, plus seeded differential fuzzing (random maps, random op logs, mutated delta files) and a permutation battery over Studio-produced real files (maps in both formats and signed deltas, apply matrix across base/target formats).

The Arkadia map data originates from the community crowd-mapping project at Delwing/arkadia-mapa.

Internationalization

The package is English-first: all code, comments and default output are English. Polish is available for user-facing output via message catalogs (src/locale.js, exported as LOCALES).

  • Opt-in per call, no global state. Pass { locale: 'pl' } as the options argument; anything else (including no options) yields English. resolveLocale(locale) maps only the exact string 'pl' to Polish and everything else to English.
  • diffMaps(src, dst, { locale: 'pl' }) — op labels in Polish, byte-identical to ArkMap Studio's history panel (including correct Polish plural forms via plural()).
  • validate(map, { locale: 'pl' }) — error/warning msg in Polish. The error shape is { path, code, msg }: code is a stable machine-readable identifier (e.g. INVALID_DIRECTION, TARGET_NOT_FOUND) independent of the locale, msg is the localized rendering.
  • .arkdelta validation and apply (validateDeltaText, applyDelta) — errors / skip reasons follow opts.locale; Polish messages are byte-pinned to ArkMap Studio. Machine codes (CHECKSUM_MISMATCH, ROOM_MISSING, …) are locale-independent.
  • .dat import errors (datToArkmap, readMudletDat) always throw English messages with a machine code property (DAT_TRUNCATED, DAT_NEGATIVE_COUNT, DAT_UNSUPPORTED_VERSION) — parser errors are developer-facing, not end-user output.
  • translate(key, params, locale) resolves a catalog key with {param} substitution (PL falls back to EN for missing keys; an unknown EN key throws). plural(locale, n, formsKey) implements CLDR plural rules (Polish one/few/many, English one/other).

License

MIT © Isithunzi000