Unreal dual-format wearable test
A sendable end-to-end test for publishing one wearable with Web and cooked Unreal package renditions.
Unreal dual-format wearable test
Send this page to the Unreal integration engineer. It tests the live transport contract, not the target interactive-item runtime.
Use https://helix-backend-api-helix3.up.railway.app with a helix3 Bearer token. Swagger is at
helix-backend-api-helix3.up.railway.app/docs.
0. Cook and package the Unreal rendition
Package the wearable for the current Win64 runtime before publishing it. For an Unreal Io Store build, use the matching Win64 packaged outputs:
<packaged-output>/Content/Paks/HelixWearable.pak
<packaged-output>/Content/Paks/HelixWearable.utoc
<packaged-output>/Content/Paks/HelixWearable.ucasDo not upload loose Content Browser .uasset files. The package files are a set: publish the
matching .pak, .utoc, and .ucas produced by the same Win64 cook.
Record the exact Unreal version that performed the cook. That value becomes engineVersion and is
how a consuming runtime decides whether the rendition is compatible. HELIX currently targets and
defaults this field to Unreal 5.7. That is a HELIX runtime target, not a requirement of the
package extensions or upload format.
A 5.5 package must be re-cooked
Unreal 5.5 packages are not compatible with the HELIX 5.7 runtime. Do not relabel an existing 5.5
cook as 5.7. Re-cook with Unreal 5.7, publish the newly cooked package set, and keep the old row
labeled 5.5. Explicit patch values such as 5.7.1 are accepted when they reflect the exact engine
used for the cook.
Win64 is the only supported Unreal target
Cook and publish only a Win64 package. HELIX does not currently support native Unreal packages for
other targets. Every Unreal asset in this test must declare targetPlatforms: ["Win64"].
Before creating the item, calculate the size and SHA-256 of all three files:
wc -c HelixWearable.pak HelixWearable.utoc HelixWearable.ucas
shasum -a 256 HelixWearable.pak HelixWearable.utoc HelixWearable.ucas1. Create one item with two source rows
Use one Universal Item with two creator-uploaded source rows: one web GLB and one unreal
packaged build. Replace every angle-bracketed value below with the actual filename, byte count, or
64-character SHA-256 from the cook before sending the request.
POST /api/v1/universal-items
Authorization: Bearer <HELIX3_TOKEN>
Content-Type: application/json{
"slug": "ue-dual-wearable-<unique-suffix>",
"title": "UE Dual Wearable Test",
"description": "Private web + cooked Unreal package contract test",
"kind": "wearable",
"category": "jackets",
"slot": "Cosmetic.Slot.Clothing.Top",
"genders": ["male", "female"],
"usage": "wear",
"contentRating": "everyone",
"contentRatingAttestationAccepted": true,
"keepPersonal": true,
"assets": [
{
"rendition": "web",
"filename": "wearable.glb",
"mimeType": "model/gltf-binary",
"sizeBytes": <GLB_SIZE_BYTES>
},
{
"rendition": "unreal",
"engineVersion": "5.7",
"targetPlatforms": ["Win64"],
"files": [
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.pak",
"size": <PAK_SIZE_BYTES>,
"mimeType": "application/octet-stream",
"sha256": "<PAK_SHA256>"
},
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.utoc",
"size": <UTOC_SIZE_BYTES>,
"mimeType": "application/octet-stream",
"sha256": "<UTOC_SHA256>"
},
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.ucas",
"size": <UCAS_SIZE_BYTES>,
"mimeType": "application/octet-stream",
"sha256": "<UCAS_SHA256>"
}
]
}
]
}Expected create result: HTTP 201; item status is draft; both source assets are
awaiting_upload; the Unreal asset reads engineVersion: "5.7" and
targetPlatforms: ["Win64"]. The response has one web uploadUrl and one
files[].uploadUrl for each of the three package files. Select both source rows from
response.item.assets by rendition; do not treat an asset row as the top-level response.
Representative Unreal response shape:
{
"item": {
"assets": [
{
"id": "<unrealAssetId>",
"rendition": "unreal",
"status": "awaiting_upload",
"engineVersion": "5.7",
"targetPlatforms": ["Win64"],
"files": [
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.pak",
"size": <PAK_SIZE_BYTES>,
"sha256": "<PAK_SHA256>",
"url": "https://<cdn>/unreal/Wearable/Win64/Content/Paks/HelixWearable.pak",
"uploadUrl": "<PRESIGNED_PUT_URL>"
},
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.utoc",
"size": <UTOC_SIZE_BYTES>,
"sha256": "<UTOC_SHA256>",
"url": "https://<cdn>/unreal/Wearable/Win64/Content/Paks/HelixWearable.utoc",
"uploadUrl": "<PRESIGNED_PUT_URL>"
},
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.ucas",
"size": <UCAS_SIZE_BYTES>,
"sha256": "<UCAS_SHA256>",
"url": "https://<cdn>/unreal/Wearable/Win64/Content/Paks/HelixWearable.ucas",
"uploadUrl": "<PRESIGNED_PUT_URL>"
}
]
}
]
}
}If x-engine-version is also sent, it overrides the engineVersion value in the body. Prefer one
truthful value unless explicitly testing that header behavior.
2. Upload the packaged outputs
There is no upload JSON after create. PUT each file's bytes to its own returned uploadUrl. Do not
send the HELIX Bearer token to R2.
PUT <webAsset.uploadUrl>
<wearable.glb bytes>Repeat this request for the .pak, .utoc, and .ucas URLs:
PUT <unrealAsset.files[n].uploadUrl>
<matching cooked package-file bytes>Each PUT should return HTTP 200.
3. Finalize each source asset
Finalize each source asset once:
POST /api/v1/universal-items/<itemId>/assets/<webAssetId>/finalize
Authorization: Bearer <HELIX3_TOKEN>
Content-Type: application/json
{"sizeBytes": <GLB_SIZE_BYTES>}POST /api/v1/universal-items/<itemId>/assets/<unrealAssetId>/finalize
Authorization: Bearer <HELIX3_TOKEN>
Content-Type: application/json
{}Fresh deployed tests returned HTTP 201 with status: "pending" from each finalize call.
Finalize means verified and queued, not ready.
Known finalize status-code discrepancy
The deployed runtime returned HTTP 201, while the deployed OpenAPI currently documents HTTP 200. Backend task CH-1162 owns the reconciliation. This page reports the observed 201 without claiming the OpenAPI discrepancy is fixed. In either case, readiness comes only from the asset status.
4. Poll until ready
GET /api/v1/universal-items/<itemId>Do not load either source rendition until its asset reports status: "ready". Select the Unreal row
from response.item.assets by rendition, engine version, and Win64 target. The ready row must still
identify the exact cook/runtime compatibility and all three package files:
{
"item": {
"assets": [
{
"id": "<unrealAssetId>",
"rendition": "unreal",
"status": "ready",
"engineVersion": "5.7",
"targetPlatforms": ["Win64"],
"files": [
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.pak",
"sha256": "<PAK_SHA256>",
"url": "https://<cdn>/unreal/Wearable/Win64/Content/Paks/HelixWearable.pak"
},
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.utoc",
"sha256": "<UTOC_SHA256>",
"url": "https://<cdn>/unreal/Wearable/Win64/Content/Paks/HelixWearable.utoc"
},
{
"key": "Wearable/Win64/Content/Paks/HelixWearable.ucas",
"sha256": "<UCAS_SHA256>",
"url": "https://<cdn>/unreal/Wearable/Win64/Content/Paks/HelixWearable.ucas"
}
]
}
]
}
}A generated thumbnail asset row may be appended asynchronously after the two source rows become
ready. It is expected derived output, not a third upload. Do not assert assets.length === 2; select
the ready web or unreal row by rendition and compatibility.
5. Download, verify, and mount
Download every selected files[].url, recompute SHA-256 locally, and require it to match
files[].sha256 before mounting. Require the complete matching .pak/.utoc/.ucas set, then
select only a rendition whose engineVersion matches the running client and whose
targetPlatforms includes Win64.
The live backend does not inspect Unreal package internals or prove runtime compatibility. A ready status means the declared bytes passed transport checks—not that Unreal can mount the package. Mount the downloaded set in the matching Unreal runtime and exercise the wearable there. If no compatible native rendition exists, Unreal may fall back to the ready web GLB only when that path is supported. Web must never select an Unreal-only rendition.
Public cdnUrl and files[].url values are durable, mutable pointers—not immutable Vault versions.
An asset update may overwrite the same object key, so invalidate caches when status or build
metadata changes.
6. Run one checksum-negative check
Create a disposable Unreal asset that declares the complete cooked .pak/.utoc/.ucas set.
Declare the real size and digest for two files, make exactly one digest intentionally wrong, then
upload the matching bytes for all three files. Its finalize must return HTTP 422:
{
"message": "Upload integrity check failed for 1 file(s); the asset was NOT finalized. Re-upload and finalize again.",
"reason": "sha256_mismatch",
"files": [
"BadDigest.pak: declared <digest>, stored object is <actual digest> (<bytes> bytes)"
]
}Delete that disposable asset after the check.
Report back
The server currently verifies transport integrity; it does not parse or mount Unreal packages, validate cook/dependencies, require a complete manifest, or provide immutable asset revisions. Please report:
- whether the downloaded complete package set mounts successfully in Unreal 5.7 on Win64;
- the exact Unreal cook version and confirmation that the target was Win64;
- the item and asset IDs used;
- the final asset statuses;
- the recomputed SHA-256 result for the
.pak,.utoc, and.ucas; - any engine-side load, dependency, naming, or compatibility failure.