Draft specification: This reference describes container version
1 in the initial costume storage implementation. The application change is not yet a released format commitment. The reference, JSON schemas, and examples are published under this documentation repository’s MIT license..costume format is intended to be open. This reference describes the storage structure so independent tools can inspect, validate, copy, package, and preserve costumes without depending on an Armorsmith SDK. Its building blocks are UTF-8 JSON, ordinary files, SHA-256, Base64, and ZIP.
The central relationship is one costume → shared assets + workspaces. A costume owns an asset collection. Each workspace lists the assets it uses. Multiple workspaces can reference the same asset ID.
For everyday saving and sharing, start with Costume files. This page explains the on-disk contract in detail.
Scope and implementation status
There are two layers:- The storage container: manifests, asset records, workspace records, file references, hashing, directory layout, and ZIP packaging.
- The content codec: the meaning of mesh bytes and product-specific scene or workspace fields.
armor-chunks compatibility codec. The initial codec preserves legacy scene data instead of introducing a new neutral mesh or scene encoding. A .bin suffix does not identify a standard vertex-buffer layout.
An independent tool can validate and republish a costume without decoding geometry. Reconstructing its legacy scene stream is also defined below. Interpreting that stream as a fully editable scene still requires support for the legacy .armor grammar and its product-specific data. This reference is not a complete specification of every historical .armor field.
Saved preview meshes, a neutral native mesh codec, and loading high-detail geometry only when needed are future work. Do not infer that they are available merely because the container supports multiple file roles.
Two physical representations
Editable directory representation
Example.costume is a JSON manifest. Its companion directory is Example, in the same parent directory. Remove only the last extension to derive the directory name: Example.v2.costume uses Example.v2/.
All manifest and record paths resolve relative to that companion directory, not relative to the manifest’s parent directory.
Published ZIP representation
A publishedExample.costume is a ZIP archive with this root layout:
Example/ directory inside the archive. Use exactly the same relative dependency paths as the editable manifest. manifest.json contains the same logical manifest that would be saved in an editable .costume file.
The current opener distinguishes the representations by the first two bytes: PK means ZIP; otherwise it parses the .costume file as JSON. Do not put a prefix before a ZIP archive. The filename suffix is checked without regard to case, but internal path spelling should be preserved exactly.
To match the current ZIP reader:
- Include a root
manifest.jsonand every declared dependency. - Store entries as regular files. Omit explicit directory entries; nested file paths are enough.
- Reject duplicate entry names, symbolic links, and unsafe paths.
- Use ordinary ZIP storage or compression supported by the reader. The format does not require a particular compression level or entry order.
- Keep large-file limits in mind. Version 1 does not promise ZIP64 support across readers.
JSON and identifiers
JSON documents use UTF-8 and must have an object at the root. Object key order and whitespace have no semantic meaning. Hashes, however, cover the exact stored bytes, including whitespace and final newlines. There is no canonical JSON serialization requirement. Costume IDs, asset IDs, workspace IDs, and file-role names use this pattern:files object. Record IDs must match the corresponding manifest key. These identifiers are distinct from display names and content hashes.
The current creator assigns the costume a UUID without braces. UUIDs are a writer convention; the accepted identifier syntax is broader. Do not interpret a display name as an identifier or silently merge two different asset IDs because their bytes happen to match.
The current Save As operation retains the costume ID in the copied document. An ID is therefore not proof that two files at different locations will keep the same content. Treat copies and later edits according to their actual manifests.
Manifest
The manifest is the costume’s top-level JSON object:
Each record reference is an object with a
path string pointing to a hashed .json file. Additional fields may be present.
workspaceOrder must contain every workspace key exactly once. Every entry must refer to an existing workspace. Duplicate or missing entries are invalid. The order of keys in workspaces is not a substitute for this array.
An empty asset collection and empty workspace collection are valid at the storage level. The current scene-opening workflow requires at least one workspace.
Small illustrative manifest
This shortened example uses<digest> placeholders and cannot be loaded as written. The downloadable examples below contain actual hashes.
Asset records and file roles
An asset record describes one reusable item:
The current writer maps asset kinds to these folders and record stems:
Each role’s file reference contains:
That maximum preserves exact integer representation in JSON consumers using IEEE-754 doubles. It does not guarantee that a particular implementation can allocate or read a file that large. Zero-byte files are valid and have the SHA-256 digest of an empty byte string.
File-role names are extensible. The storage layer can retain an unfamiliar role without opening it. A role called
source or preview has no universal byte encoding unless its codec defines one. The initial bridge writes source for model chunks and header/footer for costume-wide state. It does not generate a standardized saved preview role.
Writers update supplied roles while preserving other roles. Changing an asset’s existing kind is rejected by the current storage API.
Workspace records
Every declared workspace asset must exist. Duplicate references are invalid. The costume-wide
costume-state asset is a bridge-level dependency, so the bridge does not need to list it separately in each workspace’s assets array.
Two workspaces sharing an asset ID use the same asset record. Deleting an asset still referenced by a workspace is rejected. Deleting a workspace removes its manifest entry and its entry in workspaceOrder; it does not automatically delete every asset or old file that once belonged to it.
Paths and content integrity
Version 1 uses content-addressed dependency filenames:sha256 field. The .costume manifest itself is not named by its digest.
Readers must verify JSON record digests before trusting their contents. When reading a binary role, verify its digest and its byte count. A record cannot point to one hash in sha256 and a different hash in its path suffix.
Independent writers can serialize JSON differently from Armorsmith, provided they calculate filenames from the bytes they actually store. Reformatting a record requires a new digest filename and an updated reference.
Paths must remain inside the costume’s dependency root:
- Use forward slashes and relative paths.
- Do not use absolute paths, drive names, colons, backslashes, NUL characters, or
..traversal. - Use clean paths without
.components, doubled separators, or trailing separators. - No path component may end with a dot or space.
- Reject symbolic links for the companion directory and any dependency component. The current manifest file must not itself be a symbolic link.
Initial armor-chunks codec
The compatibility bridge serializes a legacy scene, then splits that byte stream into costume-wide state, workspace fragments, and owned model blocks. It preserves raw bytes so migration can round-trip legacy scene information.Costume-wide state
The bridge writes an asset with:size values describe the real bytes.
header contains the legacy bytes before the first workspace. footer contains the bytes after the last workspace, including saved avatar data when present. A role may be empty but is still represented by a real hashed file.
Model assets
Each owned legacyMESH block becomes a model asset with codec: "armor-chunks". Its source role contains the complete raw block, including its framing and line endings. It may include legacy-encoded mesh arrays and processed geometry; it is not an OBJ file or a newly defined native mesh buffer.
An initial bridge asset ID is derived from its owning workspace’s numeric GUID and the mesh’s hexadecimal MSID, for example model-101-abc. If the mesh ID is absent, the bridge uses its local ordinal. A collision can add an ordinal suffix. A workspace without a legacy GUID also falls back to an ordinal.
These derived names describe the initial adapter. Generic container readers should treat IDs as opaque and should not try to derive ownership or geometry from their spelling.
The bridge resolves an existing shared mesh reference using its owner workspace name, mesh name, and resource hash. It rejects a missing or ambiguous owner. This preserves legacy sharing; it does not automatically deduplicate independently imported models with similar names or identical geometry.
Workspace layout
Each bridge workspace hascodec: "armor-chunks" and a legacyLayout array. Items have two forms:
- A string containing standard Base64-encoded raw bytes. Decode strictly, without changing the bytes.
- An object of the form
{"asset": "model-101-abc"}. Read that asset’ssourcerole and insert its complete bytes at that position.
assets array. Its asset must exist and provide the source role. The Base64 strings can be empty.
An owned model is replaced by an insertion object. A legacy redirect to another workspace’s model remains a literal Base64 fragment, while the workspace lists the referenced shared asset in assets. Therefore a workspace’s asset list can contain a model with no insertion object in that workspace’s layout.
Reconstruct the legacy stream
The complete reconstruction algorithm is:23. That legacy scene version is distinct from container version: 1. A neutral future scene codec will need its own encoding definition and compatibility rules.
Locked legacy costumes
The container does not define ZIP encryption. Legacy password restrictions remain inside thearmor-chunks scene data, and the bridge retains the encoded bytes. The conversion bridge uses the legacy line decoder to recognize workspace and mesh boundaries, while storing the original byte fragments.
The historical LOCK line has two restriction flags and a 64-character hexadecimal value. Container reconstruction can preserve a locked legacy stream without decoding its geometry. A scene reader must implement the legacy locking behavior to interpret it correctly.
Manifest labels and JSON record metadata are still readable. This container does not define a new password encryption system, cryptographic authentication, or confidentiality guarantee.
Reader workflow and lazy access
A storage reader can open a costume without reading its full mesh payloads:- Determine JSON versus ZIP representation.
- Parse the manifest and check its format, version, IDs, and collections.
- Read referenced asset and workspace JSON records and verify their hashes and matching IDs.
- Validate kinds, file references, workspace references, and workspace order.
- Check that every declared dependency exists.
- Load a binary role when requested, then verify its size and SHA-256.
Writer workflow and atomic saves
For an editable costume, write new content-addressed dependencies before replacing the manifest:- Acquire a cooperative write lock for the target costume.
- Compare the current manifest bytes with the baseline from when the costume was opened. Refuse an unexpected change or removal.
- Stage modified payloads and JSON records under their new digest filenames. Update the in-memory index.
- Write and commit those dependency files. Keep unchanged files as they are.
- Recheck the manifest baseline.
- Atomically replace the manifest as the final commit step.
- Release the lock and update the saved baseline.
<filename>.lock file through Qt’s QLockFile and atomic file replacement through QSaveFile. The lock’s contents are a Qt coordination detail, not portable costume data. Independent writers must coordinate with other writers; the baseline check alone cannot prevent every race between uncooperative processes. Shared or cloud-synchronized folders do not guarantee atomic transactions across machines.
Old immutable dependency files are retained. Version 1 has no automatic garbage collection or user-facing revision history. Do not remove old files as part of an ordinary save. A future cleanup tool must account for every manifest, backup, and external reference it intends to preserve.
Save As copies every declared dependency into the new companion directory, including file roles that were never opened for editing. It must preserve unknown data. A new destination should be empty; replacing an existing costume requires an explicitly established overwrite baseline. The current API refuses saving a published ZIP in place as an editable costume.
For a ZIP, build a complete package in a temporary destination and atomically replace the requested output only after all dependencies and archive writing succeed. Keep publishing separate from the active editable file.
Extensions and compatibility
Preserve unfamiliar JSON fields in manifests, references, assets, and workspaces. Preserve unfamiliar file roles and their bytes when copying or saving. A tool that edits one workspace should not erase another product’s fields simply because it cannot interpret them. Reserve the documented structural fields for their stated meanings. Product-specific fields should use a distinctive name or a nested product object to avoid collisions. That naming advice is a convention, not a required version 1 extension registry. Current container readers reject aversion other than 1 and asset kinds outside the five listed above. Do not add a new asset kind and assume every version 1 reader will accept it. The container can preserve unfamiliar codec strings, but the current scene adapter rejects workspace codecs other than armor-chunks when opening a scene.
Changes to structural meaning need an explicit container version decision. Changes to content encoding need an explicit codec definition. There is no silent downgrade rule, standardized unit system, neutral coordinate convention, or required material/texture/pattern codec in this first container specification.
Sharing a container format between Armorsmith and Clothsmith does not make every product-specific workspace feature interchangeable. Preserve data you cannot interpret and report unsupported editing features clearly.
Public schemas and reproducible examples
The documentation repository contains JSON Schemas for version 1:manifest.schema.json: top-level costume index.asset.schema.json: shared asset records and binary file references.workspace.schema.json: workspace IDs and asset references.armor-workspace.schema.json: the initial workspace compatibility layout.common.schema.json: reusable identifiers and path constraints.
Example.armor byte for byte.
Independent implementations should exercise at least: shared asset references; unknown fields and roles; empty payloads; Save As without opening source geometry; damaged records and payloads; missing files; duplicate or incomplete workspace order; unsafe paths; duplicate ZIP entries; interrupted writes; stale manifest baselines; and exact legacy-stream reconstruction.
