.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
.asundersuffix. - The loader also accepts
.zipcontainers 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.