← Open file formats
Markmaker format

.asunder

ZIP-based archive format with a plugin-keyed JSON document, top-level raster tile binaries, and optional plugin-owned binary payloads.

Contents

1. Container layout

ZIP archive with the .asunder suffix. The real format contract is the archive structure and the data inside it.

project.asunder
├── manifest.json
├── document.json
├── t_x_y.bin
└── plugins/
    └── <pluginId>/
        └── <plugin-relative files>

The shared serialization manager writes manifest.json and document.json at the top level, keeps raster engine tiles as top-level t_* files, and routes every other binary payload under plugins/<pluginId>/....

2. manifest.json

manifest.json is the top-level index for the document. It records the shared format version and the list of serialized plugin payloads expected in document.json.

{
  "version": "1.0.0",
  "plugins": [
    { "name": "SYSTEM_CONFIG@1", "version": "1.0.0" },
    { "name": "RASTER_ENGINE@1", "version": "1.0.0" },
    { "name": "LAYER_SYSTEM_SERVICE@1", "version": "1.0.0" }
  ],
  "layerCount": 6,
  "createdAt": "2026-03-15T00:00:00.000Z",
  "modifiedAt": "2026-03-15T00:00:00.000Z"
}

version

The document container version. Current files write 1.0.0.

plugins

Array of plugin names and versions. Every plugin listed here must also appear in document.json with the same name and version.

layerCount

High-level document metadata. It is not the only place layer data appears.

createdAt and modifiedAt

Document timestamps written into the shared manifest.

The loader rejects malformed manifests, duplicate plugin entries, non-string versions, and manifest/document mismatches.

3. document.json

document.json is not one monolithic schema. It is a map keyed by plugin token IDs, and each plugin owns its own serialized payload.

{
  "SYSTEM_CONFIG@1": { ... },
  "RASTER_ENGINE@1": { ... },
  "LAYER_SYSTEM_SERVICE@1": { ... }
}

Entry envelope

{
  "pluginName": "RASTER_ENGINE@1",
  "version": "1.0.0",
  "data": {
    "canvas": { ... },
    "tileGrid": { ... },
    "background": { ... },
    "layers": { ... },
    "tiles": { ... }
  }
}

Each entry in document.json is expected to contain a pluginName, a version, and a data payload. The entry key must exactly match the pluginName. Missing payloads or mismatched keys are treated as invalid files.

For reverse engineering, treat the archive as a two-level schema: first resolve the plugin entry envelope, then parse the plugin-owned data object. The serializer does not flatten those payloads into one global namespace.

4. Document entry families

A .asunder archive is a set of plugin-owned entries. Those entries fall into a few predictable families.

Core document entries

SYSTEM_CONFIG@1, RASTER_ENGINE@1, and LAYER_SYSTEM_SERVICE@1 define the document identity, raster layer/tree data, and tile payload references.

Editor-state entries

Entries such as RASTER_VIEWPORT_STATE@1, ACTIVE_TOOL_STATE@1, RASTER_BRUSH_STATE@1, COLOR_PALETTE_STORE@1, and FX_STATE_STORE@1 store additional editor state.

Binary-backed entries

RASTER_ENGINE@1 references top-level t_x_y.bin tile files. Other entries may also reference plugin-owned binaries under plugins/<pluginId>/....

Unknown entries

A parser should not assume only the entries listed on this page will ever appear. The envelope format allows additional plugin entries to coexist with the core document data.

5. RASTER_ENGINE@1

RASTER_ENGINE@1 is the raster image payload. It describes the canvas, tile grid, background, layer-slot mapping, and the manifest of saved tile binaries.

{
  "canvas": { "width": 2048, "height": 1536 },
  "tileGrid": { "width": 2048, "height": 1536 },
  "background": { "r": 255, "g": 255, "b": 255, "a": 255 },
  "layers": {
    "count": 6,
    "maxPhysicalSlot": 5,
    "logicalToPhysical": [0, 1, 2, 3, 4, 5],
    "metadata": [ ... ]
  },
  "tiles": {
    "manifest": [
      {
        "coords": [0, 0],
        "hasLayers": [0, 2, 3],
        "filename": "t_0_0.bin"
      }
    ]
  }
}

canvas

User-requested canvas dimensions in pixels.

tileGrid

Tile-aligned document dimensions.

background

RGBA background color. Current files persist this explicitly.

layers.count and layers.maxPhysicalSlot

Counts needed to interpret the packed tile format and the layer-slot mapping.

layers.logicalToPhysical

Logical-layer to physical-slot mapping.

layers.metadata

Per-layer engine metadata including name, type, blend mode, opacity, visibility, lock state, alpha-lock, clip-to-below, and optional group fields.

tiles.manifest

One entry per non-empty tile. Each entry includes tile coordinates, the physical slots present in that tile, and the matching binary filename.

Note

This entry is not the full layer tree by itself. Group structure and selected-layer state are described separately by LAYER_SYSTEM_SERVICE@1.

Layer metadata shape

Each entry in layers.metadata is a flat engine record with id, name, type, blendMode, opacity, visible, locked, alphaLocked, and clipToBelow. Group records may also carry collapsed and children. Layer records do not.

Tile manifest shape

Every tiles.manifest entry is exactly: { coords: [ix, iy], hasLayers: number[], filename: string }. coords are tile coordinates, not pixel coordinates. hasLayers uses physical slot indices, not logical layer indices.

6. LAYER_SYSTEM_SERVICE@1

The raster layer management payload stores the canonical layer tree, not just flat engine slot metadata. This is where groups, selected layers, and the last-clicked layer live.

{
  "rootItems": [
    {
      "id": "layer_a",
      "name": "Layer 1",
      "type": "layer",
      "payload": {
        "contentKind": "bitmap",
        "blend_mode": 0,
        "opacity": 1,
        "clip_to_below": false,
        "alpha_locked": false,
        "visible": true,
        "locked": false,
        "locked_override": false,
        "visible_override": false
      }
    }
  ],
  "selectedIds": ["layer_a"],
  "lastClickedId": "layer_a"
}

rootItems is the top-level layer tree. Each item is either a raster layer or a group. Group children contain layer items.

Layer item

Carries id, name, type: "layer", and a payload containing content kind, blend mode, opacity, visibility, lock state, alpha-lock state, clip-to-below state, and override flags.

Group item

Carries id, name, type: "group", a group payload, and children for layer items.

selectedIds

Current layer selection set.

lastClickedId

Current primary/anchor layer for selection behavior.

Group payloads are smaller than layer payloads. They currently carry visible, locked, and collapsed. Raster layer payloads may also describe non-bitmap adjustment layers via contentKind: "adjustment", adjustmentType, and adjustmentParams.

7. SYSTEM_CONFIG@1

SYSTEM_CONFIG@1 stores the top-level document identity and high-level canvas settings that are serialized with the file.

documentId

Stable document identifier.

Colors and DPI

Primary color, secondary color, and document DPI.

canvas and tileGrid

Canvas dimensions and tile-aligned engine dimensions.

Background alpha and color space

canvasBackgroundAlpha and documentColorSpace.

zoom

Zoom sensitivity metadata.

{
  "documentId": "8f6f8f0c-1f44-4b2b-b1b4-6af17a9f4f2d",
  "activePrimaryColor": [100, 150, 255],
  "activeSecondaryColor": [255, 255, 255],
  "dpi": 300,
  "canvas": { "width": 2048, "height": 1536 },
  "tileGrid": {
    "width": 2048,
    "height": 1536,
    "tilesX": 8,
    "tilesY": 6
  },
  "canvasBackgroundAlpha": 1,
  "documentColorSpace": "srgb",
  "zoom": {
    "scrubPxPerStep": 4,
    "wheelStepMultiplier": 2,
    "trackpadPinchStepMultiplier": 2
  }
}

This entry stores only the document-relevant subset of system configuration. Session-only fields are outside the file contract.

8. Optional editor-state entries

Files may also contain editor-state entries alongside the core image payload: tool selection, viewport state, brush settings, selection summaries, palette state, and last-used filter parameters.

RASTER_VIEWPORT_STATE@1

Stores viewport position, raw zoom value, and rotation.

ACTIVE_TOOL_STATE@1

Stores the current primary tool identifier.

RASTER_BRUSH_STATE@1

Stores a brush parameter block, including size, spacing, opacity, hardness, tilt/pressure toggles, fill parameters, wrap mode, and pattern-fill controls.

RASTER_SELECTION_SERVICE@1

Stores selection summaries, not full selection-tile bytes.

COLOR_PALETTE_STORE@1 and FX_STATE_STORE@1

Store palette data, color history, and filter parameter records.

Common small-payload shapes

{
  "RASTER_VIEWPORT_STATE@1": {
    "pluginName": "RASTER_VIEWPORT_STATE@1",
    "version": "1.0.0",
    "data": {
      "position": [0, 0],
      "zoom_raw": 0,
      "rotation": 0
    }
  },
  "ACTIVE_TOOL_STATE@1": {
    "pluginName": "ACTIVE_TOOL_STATE@1",
    "version": "1.0.0",
    "data": { "primaryTool": "draw" }
  }
}

Common compact payloads include: [email protected] = { position, zoom_raw, rotation }, [email protected] = { primaryTool }, [email protected] = { savedPalettes, currentPaletteName, colorHistory }, and [email protected] = { lastFilterId, lastParameters }.

Selection summary shape

RASTER_SELECTION_SERVICE@1 currently persists selection summaries rather than full 256×256 mask tile bytes.

{
  "selections": {
    "sel_1": {
      "id": "sel_1",
      "name": "Selection 1",
      "createdAt": 1741996800000,
      "bbox": { "x": 0, "y": 0, "w": 512, "h": 512 },
      "occupiedTiles": [0, 1, 8, 9]
    }
  }
}

The summary includes an ID, optional name, creation timestamp, bounding box, and the list of occupied tile indices.

That is an important boundary for reverse engineering: current .asunder files do not contain persisted selection mask bytes, only selection summaries and occupied tile indices.

9. Tile binaries

Non-empty raster tiles are saved as top-level t_x_y.bin files. Empty tiles are omitted from the archive and therefore do not appear in tiles.manifest.

Filename rule

Tile files are named t_<ix>_<iy>.bin using tile-grid coordinates.

Manifest link

Every saved tile binary is referenced from RASTER_ENGINE@1 via coords, hasLayers, and filename.

Current packed header

The current packed tile format begins with an 8-byte header: tile-format version, number of stored layers, bitmap length, and reserved bytes.

Slot bitmap

After the header, the file stores a bitmap that marks which physical layer slots are present in this tile.

Per-slot payloads

Each present slot then stores a 1-byte compression flag, a 4-byte byte length, and the slot's raw pixel payload. Current files use uncompressed slot payloads.

Interpretation rule

Each binary is interpreted against the matching tiles.manifest entry and the layers.logicalToPhysical mapping.

Non-tile binaries use the generic plugin path layout under plugins/<pluginId>/.... Those files are owned by whichever plugin emitted them, not by the raster engine.

Byte layout

Offset  Size  Type      Meaning
0x00    2     uint16    tile format version (current: 2)
0x02    2     uint16    stored layer count
0x04    2     uint16    slot bitmap size in bytes
0x06    2     uint16    reserved, currently 0
0x08    n     bytes     slot-presence bitmap
...     1     uint8     compression flag per stored slot (current value: 0)
...     4     uint32    payload byte length for that slot
...     m     bytes     slot payload

Numeric header fields are currently written with DataView defaults, so they are big-endian.

Tile manifest record

Every tile manifest record has the same three fields: coords for tile-grid position, hasLayers for the physical slots present in that tile, and filename for the matching top-level binary.

The slot bitmap is scanned in ascending physical-slot order. The per-slot payloads immediately following that bitmap use the same order. A parser does not need any delimiter beyond the stored layer count and each payload's explicit byte length.

10. Plugin-owned binaries

Non-tile binary payloads are routed under plugins/<pluginId>/.... The serialization system treats those files as plugin-owned and only special-cases top-level raster tile binaries.

Routing rule

A plugin that returns binary files gets those files written under its plugin token ID, preserving the plugin-relative filenames it emitted.

Current examples

The format allows plugin-specific binary outputs in addition to top-level raster tiles.

Strict pairing

If plugin files exist in the archive but the matching plugin JSON entry is absent from document.json, the archive is internally inconsistent.

Filename authority

The plugin-relative filename carried in JSON is the authoritative key for matching a binary payload to the entry that owns it.

11. Consistency rules

A valid .asunder file is more than a ZIP that happens to contain JSON. The top-level entries, plugin envelopes, and binary references need to agree with one another.

Manifest/document coherence

manifest.json and document.json must agree on plugin names and versions.

Entry coherence

The key in document.json must match the entry’s pluginName. Missing envelopes or mismatched names make the file internally inconsistent.

Tile references

Every tiles.manifest filename should exist as a top-level binary, and the binary should decode to the number of slot payloads claimed by its header and bitmap.

Slot references

hasLayers contains physical slot indices, so it must be interpreted against the physical-slot mapping.

Plugin binaries

Plugin-owned binaries under plugins/<pluginId>/... should have a matching JSON entry that declares them.

Type/shape sanity

Fields such as dimensions, counts, RGBA channels, and tile headers must have sane numeric values for the file to be interpretable.

12. Compatibility and versioning

  • Files use the .asunder suffix.
  • The loader also accepts .zip containers with the same internal layout.
  • The shared document manifest currently writes container version 1.0.0.
  • Legacy files with empty plugin version strings are still accepted, but load with a warning.
  • Legacy files missing a background field default to rgba(125, 125, 125, 255) on load.
  • The loader does not currently branch into separate historical codepaths by document version.
  • The extension is only the wrapper. The real format contract is the archive structure inside.