# Publishing to Roblox

Roblox games keep their images, audio and models in Roblox's cloud, not in Git. Roblox gives you an asset id and little else: no record of who made the file or how, no link to the brief, and a new id every time an image or sound changes. agh keeps all of that. Connect a Roblox group, and publishing uploads accepted assets there, records each one's Roblox id beside its brief, maker and provenance, and keeps the history of ids as new versions arrive.

A project can publish to Roblox alone, or to Roblox and a GitHub repository together, which suits teams that build with Rojo.

## Connect a group

Assets are uploaded to a Roblox group, so the team owns them rather than one person's account.

1. Pick the account that owns the key. Roblox recommends a **user** key over a group key, made on an alternate account rather than your own. Make that account a member of the group, in a role that can create and manage the group's assets. Then, signed in as it, open **Open Cloud > API Keys** on the Creator Dashboard and create a key. A group key works too.
2. Add the **assets** API with **Read** and **Write**. To adopt assets that are already on Roblox, also add **legacy-asset** with **Manage**, which lets agh download them.
3. Under accepted IP addresses, add `0.0.0.0/0`. agh runs in AWS Lambda, which doesn't have a fixed address.
4. In agh, open **Settings › Publishing**, choose **Set up** on **Roblox**, fill in the group's id (the number in `roblox.com/groups/<id>`) and the key, then choose **Save**.

Whoever owns the key, agh uploads with the group as the creator, so the group owns every asset. With a user key, uploads stop working if that account leaves the group or loses its role; give the group a new key, or the account its role back.

agh keeps the key in AWS Secrets Manager, never on the project record, and shows only its last four characters. To change the key, save a new one. To stop, choose **Stop uploading to Roblox**: agh forgets the key, and assets already on Roblox stay there.

From the CLI, the key comes from an environment variable, so it stays out of your shell history:

```text
AGH_ROBLOX_API_KEY=... agh project roblox 7654321
agh project roblox 7654321 --luau-path src/shared/Assets.luau   # keep the key, change the module path
agh project roblox --off
```

## What goes to Roblox

| agh kind | Roblox takes | Notes |
|---|---|---|
| image | PNG, JPEG, BMP, TGA | Uploaded as an Image, smaller than 8000×8000 |
| audio | MP3, OGG, WAV, FLAC | Up to 7 minutes |
| model | FBX, glTF (`.glb`, `.gltf`), `.rbxm` | Arrives as a package of MeshParts |

Each file can be at most 20 MB. A file Roblox won't take, such as a WebP image, stays accepted, with the reason in the publish result and the asset's conversation; deliver a file in a format it takes. Text, strings and other files don't go to Roblox: with GitHub connected they go there as usual, and without it they wait.

The asset shows on Roblox under its agh id. Its description is the brief, followed by a line such as `agh: ui.icons.coin · ai · Flux`, so anyone who finds it in the Toolbox can trace it back.

## Publishing

Publish as usual from **Publish** or with `agh publish`. agh uploads each asset, then waits a few seconds for Roblox to finish. Roblox usually does; anything it hasn't finished is listed as still processing.

- **Roblox alone:** an asset is published once Roblox has it. Ones still processing are published on their own: **Publish** checks each time it opens, and so does `agh publish --status`.
- **Roblox and GitHub:** agh uploads first, then opens its pull request with the files, `.agh/provenance.yaml` and a Luau module of Roblox ids. An asset still processing on Roblox stays accepted; publish again once it's done to add it to the pull request.

Roblox moderates every upload. An asset in moderation already has its id. If moderation rejects it, the asset's conversation says so: ask for a revision to replace it.

Roblox limits audio uploads to 100 a month for an ID-verified creator, and 10 for one that isn't. If you hit the limit, Roblox's refusal shows up like any other.

## New versions

Ask for a revision to replace a published asset, as with any other. When the new version is published:

- an **FBX model** is updated in place: same asset id, new version, so places that use the package pick it up;
- **any other kind** gets a new asset id, since Roblox can't replace an image's or a sound's content. agh keeps the ids it had before in the asset's history, and the Luau module points at the new one.

## The Luau module, for Rojo projects

When the project also publishes to GitHub, every pull request rewrites `src/shared/AghAssets.luau` (or the path you set). It maps each agh asset id to its current Roblox content id:

```lua
-- Generated by agh (Asset Goes Here) on every publish. Hand edits are overwritten.
return {
	["sfx.coin_pickup"] = "rbxassetid://1234567890",
	["ui.icons.coin"] = "rbxassetid://2345678901",
}
```

With Rojo's default project, `src/shared` syncs to `ReplicatedStorage.Shared`, so code reads ids like this:

```lua
local Assets = require(game.ReplicatedStorage.Shared.AghAssets)
icon.Image = Assets["ui.icons.coin"]
```

The image, audio or model file is committed too, at the asset's path, so the source stays in Git (use Git LFS for large files). This is the same idea as Asphalt's lockfile: code refers to agh's ids, and a merge moves the whole game to the new Roblox ids at once.

## Adopting assets already on Roblox

A game made before agh already has images, sounds and models on Roblox. Adopting brings them into agh, so they have a record, a brief and a history like everything else, without uploading anything. Admins and developers adopt, since adopted assets count as published.

Roblox can't list a group's assets, so agh adopts them by id. There are two ways to find the ids:

- **From the repository.** agh reads the scripts, Rojo files (`.luau`, `.lua`, `.json`, `.toml`) and XML models (`.rbxmx`, `.rbxlx`) on the project's branch for `rbxassetid://123` and `roblox.com/asset/?id=123`, and lists each id with where it's used. When the code names it, as in `GoldCoin = "rbxassetid://123"` in `Icons.luau`, the suggested id is `icons.gold_coin`.
- **By pasting.** Copy ids from the Creator Dashboard or Studio's Asset Manager and paste them in.

On the web page, open **Publish** and scroll to **Already on Roblox**. Choose **Find Roblox ids**, or paste ids and choose **List them**, then untick anything you don't want and choose **Adopt selected**. From the CLI:

```text
agh adopt-roblox --dry-run                  # the ids the repository uses, and which agh tracks
agh adopt-roblox --folder src/shared        # adopt the ones used there
agh adopt-roblox 1234567890 2345678901 --provenance human --made-by "Jane Doe"
```

For each asset, agh downloads its current version, keeps a copy, and records its Roblox id and revision:

- an **image** or **sound** comes in as its file (PNG, JPEG, BMP, OGG, MP3, WAV or FLAC);
- a **decal** comes in as the image it shows;
- a **model** comes in as `.rbxm` or `.rbxmx`, and a **mesh** as Roblox's `.mesh`. agh can't draw these, so they show no picture unless someone delivers a new version with one.

The asset's id comes from the name the code gives it, or else from its name on Roblox, and its brief from its description. Its path, used if it is ever committed, is under `roblox/`. How it was made is *unknown* unless you say; the AI disclosure lists every unknown asset for you to record.

An adopted asset already has its Roblox id, so publishing never uploads it again, and the Luau module lists it, so code can move from raw ids to `Assets["icons.gold_coin"]` at its own pace. Ask for a revision to replace it: the new version is uploaded as usual. Animations, places, badges and other types are skipped, and so is anything agh's key can't download, such as another creator's asset.

## Not yet

- A Roblox Studio plugin to browse, insert and request assets from Studio.
- Uploading to a user's own account rather than a group.
- An Asphalt-compatible lockfile, for teams moving from Asphalt.
- A 3D preview of `.rbxm`, `.rbxmx` and `.mesh` files. Until then, deliver them with a picture (`agh fill ... --preview shot.png`), which reviewers see labelled as the artist's render. FBX and glTF models already open in a 3D viewer.
