Mapper Guide

Assets

Assets

This is the canonical v1 contract for MTADM assets. The MTADM map editor, game client, game server, website backend, and frontend should follow this document when creating, uploading, validating, resolving, and loading reusable assets.

Assets are first-class content. They may be uploaded alone as .mtaasset packages or bundled inside .mtamap map source packages.

The important architecture rule is that an asset version is a package-level content unit. A compound object with collision, mesh, material, textures, and preview files is one asset version unless the editor intentionally references separate existing asset versions for those parts.

Core Model

Use these concepts consistently.

Concept Meaning
assetId Stable backend ID for a logical asset page.
assetVersionId Stable backend ID for one immutable asset version.
localAssetId Stable editor-local ID inside one package, for example road_barrier.
assetPackageHash SHA-256 of the uploaded .mtaasset package.
deterministicHash SHA-256 over gameplay-affecting asset data. Empty for purely visual assets.
contentHash SHA-256 over the canonical content for a version. For v1 this may equal assetPackageHash.
visibility public or private. Controls discoverability and reuse, not whether a map runtime can include the asset.
root Folder inside a map package that contains a bundled asset package.
entrypoint Main metadata file relative to the asset package root.

Important rules:

  • Published maps reference exact assetVersionId values.
  • Published maps never reference "latest asset".
  • Asset versions are immutable.
  • Private assets are still normal assets.
  • Filenames, slugs, and display names are never identity.
  • Backend and game calculate hashes themselves.
  • Do not model every internal file as a separate asset by default.
  • Internal files inside a compound .mtaasset are part of that asset version.

Supported Creation Paths

Assets can enter the platform in two ways.

Standalone asset upload:

Mapper uploads road_barrier.mtaasset
  -> backend validates asset package
  -> backend creates or updates logical asset
  -> backend creates immutable asset version
  -> maps can reference that asset version

Bundled map upload:

Mapper uploads done.mtamap
  -> backend parses source/assets.json
  -> backend resolves existing asset versions
  -> backend registers missing bundled assets
  -> map release links to exact asset versions

Both paths must create the same asset/version records. The source of the upload differs, but the resulting asset model is identical.

Package Type: .mtaasset

.mtaasset is the editable/source upload format for reusable assets.

For v1, .mtaasset is a zip/container with normalized paths and JSON manifests.

Required root file:

manifest.json

Example compound object asset:

road_barrier.mtaasset
  manifest.json
  object.json
  collision.glb
  mesh.glb
  material.json
  textures/
    barrier_diffuse.webp
    barrier_normal.webp
  shaders/
    barrier.shader
  preview/
    thumbnail.webp

This entire package is one asset version. It may contain multiple files.

Internal package files are not reusable as separate platform assets unless they are also uploaded or referenced as standalone asset versions. For example, a road barrier object can include textures/barrier_diffuse.webp privately inside its own package. If the mapper wants that texture to be reusable by other assets, the texture should be uploaded as its own texture asset version and referenced as an external dependency.

Asset Package Manifest

manifest.json describes the asset package.

{
  "format": "mtadm.asset.source",
  "formatVersion": 1,
  "type": "object",
  "name": "Road Barrier",
  "slug": "road-barrier",
  "description": "Concrete road barrier with collision and visual mesh.",
  "visibility": "public",
  "usage": [
    "collision",
    "visual"
  ],
  "entrypoint": "object.json",
  "dependencies": [
    {
      "assetVersionId": "33333333-3333-3333-3333-333333333333",
      "usage": "texture",
      "required": true,
      "sourcePath": "textures/albedo"
    }
  ],
  "createdWithGameVersion": "0.3.0",
  "minGameVersion": "0.3.0",
  "physicsVersion": "phys_1",
  "determinismVersion": "det_1"
}

Field rules:

Field Required Rule
format yes Must be mtadm.asset.source.
formatVersion yes Positive integer. Starts at 1.
type yes One supported asset type.
name yes Display name. Not identity.
slug recommended URL suggestion. Backend may alter it.
description no User-facing description.
visibility yes public or private.
usage yes One or more supported usage values.
entrypoint type-dependent Main metadata file inside the package.
dependencies no External asset versions required by this asset. Each dependency must reference an existing asset version UUID.
createdWithGameVersion recommended Editor/game version that exported the package.
minGameVersion recommended Minimum game version that can load this asset.
physicsVersion required if deterministic Physics version used by deterministic asset data.
determinismVersion required if deterministic Determinism contract version.

Asset Types

Supported v1 asset types:

Type Purpose
object Reusable placed map object, often compound.
mesh Visual mesh asset.
texture Image texture.
material Material definition referencing textures/shaders.
shader Client-side visual shader source or compiled shader.
sound Audio file.
script Script asset. Public scripting policy is not finalized.
thumbnail Preview image.
other Fallback only when no specific type exists.

Use object for a reusable building block that can include collision, mesh, material, textures, and shader files in one package.

Internal Files And External Dependencies

Asset packages can reference files in two ways.

Internal package file:

{
  "mesh": "mesh.glb",
  "collision": "collision.glb",
  "materials": [
    "material.json"
  ]
}

External asset dependency:

[
  {
    "assetVersionId": "33333333-3333-3333-3333-333333333333",
    "usage": "texture",
    "required": true,
    "sourcePath": "textures/albedo"
  }
]

Rules:

  • Internal file paths are relative to the asset package root.
  • External dependencies must reference exact asset version UUIDs.
  • usage must be one supported usage value.
  • required defaults to true when omitted.
  • sourcePath is optional and describes where the dependency is used inside the dependent asset.
  • External dependencies must be included in the runtime manifest.
  • Deterministic external dependencies must be included in deterministic hashing.
  • Public/private access rules apply to dependency resolution.
  • A public asset must not depend on a private asset.

Usage Values

Supported v1 usage values:

Usage Meaning
collision Affects collision or physics. Deterministic.
visual Client visual data. Not deterministic by default.
texture Texture data. Visual by default.
material Material data. Visual unless it changes physics material data.
shader Shader data. Visual only for v1.
sound Sound data. Visual/client-side for v1.
script Script data. Deterministic only if server-side.
thumbnail Preview image.
other Fallback only.

One asset can have multiple usages.

Example:

{
  "type": "object",
  "usage": [
    "collision",
    "visual"
  ]
}

Object Assets

Object assets are reusable map building blocks.

Example object.json:

{
  "format": "mtadm.asset.object",
  "formatVersion": 1,
  "id": "road_barrier",
  "name": "Road Barrier",
  "mesh": "mesh.glb",
  "collision": "collision.glb",
  "materials": [
    "material.json"
  ],
  "bounds": {
    "size": [
      10,
      1,
      2
    ]
  },
  "physics": {
    "material": "concrete",
    "friction": 0.85,
    "restitution": 0.05
  }
}

Rules:

  • Paths inside object.json are relative to the package root or object folder.
  • collision is deterministic when the map uses the object with collision enabled.
  • mesh is visual unless it is also used as collision.
  • Physics material data is deterministic.
  • Changing collision or physics data creates a new asset version and a new deterministic map release when used by a published map.

Texture Assets

A texture asset can be a standalone .mtaasset or a file inside a compound object package.

Allowed v1 formats:

.png
.webp
.jpg
.jpeg

Recommended standalone texture package:

asphalt_diffuse.mtaasset
  manifest.json
  texture.webp
  preview/
    thumbnail.webp

Texture assets are visual by default and should not affect deterministic validation.

Material Assets

Material assets describe how textures and shaders are used.

Example:

{
  "format": "mtadm.asset.material",
  "formatVersion": 1,
  "name": "Asphalt",
  "textures": {
    "albedo": "textures/asphalt_diffuse.webp",
    "normal": "textures/asphalt_normal.webp"
  },
  "shader": "shaders/asphalt.shader",
  "parameters": {
    "roughness": 0.8,
    "metallic": 0
  }
}

Material data is visual unless it contains physics material data used by the server.

Shader Assets

Shaders are supported as assets, but v1 shader policy is deliberately strict.

Rules:

  • Shaders are client-side visual assets by default.
  • Shaders must not affect deterministic gameplay in v1.
  • Supported shader formats and features must be allowlisted by the game.
  • Server runtime must not execute or depend on client shaders.
  • If a future shader affects gameplay, it must be treated as deterministic data and included in map release hashing.

Allowed v1 extension:

.shader

Sound Assets

Allowed v1 formats:

.ogg
.wav

Sound assets are client-side by default and do not affect deterministic validation in v1.

Future validation should check:

  • duration
  • file size
  • sample rate
  • channel count

Private Assets

Private assets are normal assets with restricted visibility.

Private assets:

  • Have assetId.
  • Have immutable assetVersionId records.
  • Can be linked to map releases.
  • Can be included in public .mtabin runtime packages if the map needs them.
  • Are visible to owner, map co-authors, and admins.
  • Are not listed publicly.
  • Are not reusable by unrelated users.

Do not create a separate "map-local asset" system. A private asset used by one map is still an asset.

Public Assets

Public assets:

  • May appear on /mapping/assets.
  • May have public detail pages.
  • May show visible map usage.
  • May be referenced by other mappers when reuse workflows exist.

Public visibility does not mean mutable. Public asset versions are still immutable.

Asset References In .mtamap

Maps reference local assets through source/assets.json.

Bundled local asset:

{
  "id": "road_barrier",
  "type": "object",
  "name": "Road Barrier",
  "root": "assets/objects/road_barrier",
  "entrypoint": "object.json",
  "assetVersionId": null,
  "usage": [
    "collision",
    "visual"
  ],
  "visibility": "private"
}

Existing backend asset version:

{
  "id": "road_barrier",
  "type": "object",
  "name": "Road Barrier",
  "root": null,
  "entrypoint": null,
  "assetVersionId": "33333333-3333-3333-3333-333333333333",
  "usage": [
    "collision",
    "visual"
  ],
  "visibility": "public"
}

Resolution rules:

  • If assetVersionId is present, backend resolves that exact immutable asset version.
  • If assetVersionId is absent, root must point to a bundled asset root inside .mtamap.
  • entrypoint is required for bundled assets unless the asset type has a single obvious file.
  • Backend validates that entrypoint stays inside root.
  • If bundled content hash already exists, backend reuses the existing asset version.
  • If bundled content hash is new, backend creates a new asset and asset version.
  • Map release links to resolved exact assetVersionId.

Do not use the old ambiguous model where path points to a random asset file. The backend needs the asset root so it can hash, validate, store, and later rebuild the complete asset package.

Placed Object References

source/map.json should reference local asset IDs while editing.

{
  "objects": [
    {
      "id": "barrier_01",
      "asset": "road_barrier",
      "position": [
        0,
        0,
        20
      ],
      "rotation": [
        0,
        90,
        0
      ],
      "scale": [
        1,
        1,
        1
      ],
      "collision": true
    }
  ]
}

The backend/runtime compiler resolves asset: "road_barrier" to an exact backend assetVersionId in .mtabin.

Runtime Representation

The generated .mtabin should include resolved asset identity in runtime_manifest.json.

Example:

{
  "assets": [
    {
      "assetId": "asset_01JABC",
      "assetVersionId": "33333333-3333-3333-3333-333333333333",
      "type": "object",
      "usage": [
        "collision",
        "visual"
      ],
      "deterministic": true,
      "contentHash": "sha256:...",
      "deterministicHash": "sha256:...",
      "entrypoint": "client/assets/33333333-3333-3333-3333-333333333333/object.json",
      "dependencies": []
    }
  ]
}

The game should load assets by assetVersionId and verify hashes before use.

Runtime rules:

  • The server only loads deterministic asset data.
  • The client can load deterministic and visual asset data.
  • Asset dependencies must be resolved before loading the dependent asset.
  • Missing required asset versions make the runtime package invalid.
  • Hash mismatches make the runtime package invalid.

Hashing

Backend and game tooling calculate hashes. User-provided hashes are never trusted.

Required hashes:

Hash Meaning
assetPackageHash SHA-256 of the full .mtaasset package bytes.
contentHash SHA-256 of canonical asset version content.
deterministicHash SHA-256 over deterministic asset data only.

For a compound object asset, deterministic hash includes:

  • collision file content
  • physics material data
  • object metadata that affects collision or physics
  • deterministic script data if allowed later
  • relevant physics/determinism version strings

Deterministic hash excludes by default:

  • textures
  • visual meshes with no collision role
  • shaders
  • sounds
  • thumbnails
  • editor-only metadata

Deduplication

Deduplicate by content hash, not filename.

Rules:

  • Same filename does not mean same asset.
  • Same content hash means same immutable asset version content.
  • Different content hash means a different asset version.
  • A map release links to exact asset versions used at publish time.

Versioning

Asset versions are immutable.

If an asset changes, create a new asset version.

Examples:

Road Barrier v1 = original collision and mesh
Road Barrier v2 = fixed collision shape
Road Barrier v3 = visual material update

Maps using Road Barrier v1 continue to reference assetVersionId for v1 until explicitly updated and republished.

Validation

Backend must validate asset packages before storing them.

Required validation:

  • archive path safety
  • required manifest fields
  • supported asset type
  • supported file extensions
  • max file count
  • max package size
  • max per-file size
  • JSON syntax
  • referenced paths exist
  • no absolute paths
  • no ../ traversal
  • no NUL bytes

Future stricter validation:

  • texture dimensions
  • mesh complexity
  • collision complexity
  • shader feature allowlist
  • sound duration/sample rate

Security

Assets are untrusted input.

Backend:

  • Never trusts user-provided hashes.
  • Never trusts filenames as identity.
  • Stores immutable versions.
  • Blocks unsafe archive paths.
  • Blocks unsupported file types.
  • Denies blocked/private downloads according to visibility policy.

Game/client:

  • Verifies .mtabin and asset hashes before loading.
  • Does not execute untrusted scripts outside the approved sandbox.
  • Treats shaders as visual only in v1.
  • Uses exact assetVersionId, not names or slugs.

Server:

  • Loads only deterministic asset data needed for authoritative gameplay.
  • Ignores visual-only assets.
  • Rejects maps whose deterministic asset hashes do not match the runtime manifest.