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
assetVersionIdvalues. - 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
.mtaassetare 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 versionBundled 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 versionsBoth 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.jsonExample 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.webpThis 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.
usagemust be one supported usage value.requireddefaults totruewhen omitted.sourcePathis 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.jsonare relative to the package root or object folder. collisionis deterministic when the map uses the object with collision enabled.meshis 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
.jpegRecommended standalone texture package:
asphalt_diffuse.mtaasset
manifest.json
texture.webp
preview/
thumbnail.webpTexture 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:
.shaderSound Assets
Allowed v1 formats:
.ogg
.wavSound 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
assetVersionIdrecords. - Can be linked to map releases.
- Can be included in public
.mtabinruntime 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
assetVersionIdis present, backend resolves that exact immutable asset version. - If
assetVersionIdis absent,rootmust point to a bundled asset root inside.mtamap. entrypointis required for bundled assets unless the asset type has a single obvious file.- Backend validates that
entrypointstays insideroot. - 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 updateMaps 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
.mtabinand 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.