- `minimal` -- Headers (`--!strict`/`--!native`/`--!optimize 2`), type annotations on all function signatures, deprecated pattern replacements only
- `minimal` -- Headers (`--!strict`/`--!optimize 2`), type annotations on all function signatures, deprecated pattern replacements, native codegen analysis
- `moderate` -- + function restructuring for inlining, import hoisting, compound operators, fastcall enablement, allocation reduction
- `insane` -- + every micro-optimization from the pattern catalog, full bytecode analysis, register pressure optimization, closure caching analysis
@ -70,7 +70,7 @@ Use the AskQuestion tool with these three questions:
| Function restructuring | No | Yes (Priority 3) | Aggressive splitting |
| Bytecode verification | After all changes | After each priority | After each change |
| Algorithmic changes | Per algo quiz | Per algo quiz | Per algo quiz |
| `@native` vs `--!native` | `--!native` unless big file | Selective `@native` on hot functions | Full per-function analysis |
| Native codegen strategy | Per-function analysis; `@native` on beneficial functions; `--!native` only if most are native-worthy | Selective `@native` on hot computational functions | Full per-function cost/benefit with budget tracking |
**Restructuring scope** scales with compilation intensity:
- **minimal**: No restructuring. Only additive changes (headers, annotations, pattern swaps).
@ -236,10 +236,11 @@ Using both the source (already read in Phase 1) and tool output from Phase 2, id
Apply changes in priority order. Re-run `luau-compile --remarks -O2 --vector-lib=Vector3 --vector-ctor=new --vector-type=Vector3 <file>` after structural changes to verify the compiler benefits.
### Priority 1 -- Headers and type safety (always apply)
### Priority 1 -- Headers, type safety, and native codegen (always apply)
**Add headers:**
- `--!strict` / `--!native` / `--!optimize 2`. (`--!optimize 2` is default in live but not Studio testing.)
- `--!strict` / `--!optimize 2`. (`--!optimize 2` is default in live but not Studio testing.)
- **Do NOT blindly add `--!native`.** Native codegen is analyzed separately (see "Native codegen strategy" below).
**Remove deoptimizers:**
- Replace `getfenv`/`setfenv` -- disables builtins, imports, and optimizations globally.
@ -260,6 +261,27 @@ Getting the file to pass `--!strict` cleanly is the single most impactful compil
- For generic code: use proper generics (`<T>`) instead of `any`. If the function truly accepts anything, use `unknown` and narrow explicitly.
- Track type coverage: count `any` in `--annotate` output. The goal is zero (or as close as practical).
**Native codegen strategy -- `--!native` vs `@native` vs neither:**
Native codegen has two limits: a 1M instruction cap per module and a per-experience memory ceiling shared across all native scripts. Applying `--!native` indiscriminately wastes both budgets on functions that don't benefit, crowding out functions that would.
1. **Classify every function** in the file by its dominant work:
- **Native-worthy**: Tight computational loops, math-heavy logic (`Vector3`/`CFrame` arithmetic, `buffer` ops, `bit32` ops, numerical algorithms, physics calculations). These get genuine speedups from JIT compilation.
- **Not native-worthy**: Roblox API calls (`Instance:Clone()`, `:FindFirstChild()`, `:GetChildren()`, event connections), table construction without computation (allocation-bound), heavy Luau library calls (`string.format`, pattern matching -- C implementations native codegen can't speed up), one-shot initialization, coroutine/task scheduling. The dominant cost is the API/library call or GC, not Luau instruction dispatch.
2. **Decision matrix**:
- **All/most functions are native-worthy** (math utility modules, physics solvers, buffer serializers): Use `--!native` on the whole file.
- **Some functions are native-worthy** (mixed scripts with hot computational loops alongside API-heavy code): Use `@native` on individual functions that benefit. **This is the common case for real game scripts.**
- **No functions are native-worthy** (typical game scripts: event handlers, UI logic, Roblox API orchestration): Skip native codegen entirely. Don't waste the budget.
3. **`@native` placement rules**:
- Place `@native` directly above the function declaration.
- Inner/nested functions do NOT inherit `@native` -- annotate them individually if needed.
- Top-level module code runs once at `require()` time; `@native` has no meaningful benefit there.
- Prioritize functions identified as hot paths in Phase 1 (RunService handlers, loop bodies, recursive functions) -- but only if their work is computational, not API-bound.
4. **Budget awareness**: Check native codegen stats from `--record-stats`. If the module's native instruction count is high, be more selective with `@native` to leave budget for other scripts in the experience.
@ -392,6 +414,7 @@ These principles govern every optimization decision:
- **Local functions for inlining** -- even at the cost of duplicating imported behavior in high-traffic code.
- **Quantify everything** -- before/after metrics for every optimization pass. No "I think this is faster" -- prove it.
- **Type annotations drive native codegen quality** -- every `any` type is a missed specialization opportunity. The JIT uses annotations directly with no runtime analysis.
- **Native codegen is a budget, not a checkbox** -- `--!native` on every file wastes the per-experience native code memory limit on functions that don't benefit. Apply `@native` selectively to computational hot functions; reserve `--!native` for genuinely math-heavy modules.
- **Event-driven over polling** -- avoid per-frame calculations when events suffice.
---
@ -484,15 +507,37 @@ The compiler narrows types after certain checks, improving both type safety and
## Reference: Native Codegen
**`@native` vs `--!native`:**
- `--!native`: entire script. Good for math-heavy utility modules.
- `@native`: per-function. Better for selective hot functions or near the 1M instruction limit.
- Inner functions do NOT inherit `@native`.
- Top-level code runs once; `@native` has minimal benefit there.
**`--!native` (whole-file) -- use sparingly:**
- Compiles every function in the file to native code, consuming the per-module 1M instruction limit and the per-experience memory budget.
- Only appropriate for files where most/all functions are computational: math utilities, physics solvers, buffer serializers, numerical algorithms.
- Wasteful on typical game scripts where most functions are Roblox API calls, table construction, or string operations -- these don't benefit from native codegen but still eat the budget.
**`@native` (per-function) -- preferred for most files:**
- Annotate only functions that perform computational work benefiting from JIT: tight loops, `Vector3`/`CFrame` arithmetic, `buffer` ops, `bit32` ops, numerical math.
- Place directly above the function declaration. Inner functions do NOT inherit it.
- Top-level code runs once at `require()` time; `@native` provides no meaningful benefit there.
- This is the correct default strategy for mixed scripts with both hot computational code and API-heavy orchestration.
**Functions that do NOT benefit from `@native`:**
- Roblox API calls (`Instance:Clone()`, `:FindFirstChild()`, `:GetChildren()`, `:WaitForChild()`, event connections) -- execution time is in C++/engine, not Luau bytecode
- Table construction without computation (allocation-bound; the allocator is C code native codegen can't speed up)
- Heavy Luau library calls (`string.format`, `string.match`, `string.gmatch` -- C implementations that native codegen can't accelerate)
- Hot paths called every frame (RunService handlers with computational work, not API orchestration)
**Hurts native:** `getfenv`/`setfenv`, wrong types to typed functions, non-numeric math args, breakpoints, size limits (64K instructions/block, 32K blocks/function, 1M/module).
**Helps native:** Type annotations (especially `Vector3`), small functions, consistent table shapes, `buffer` ops, `bit32` ops.
**Budget management:** The 1M instruction limit is per-module; the memory budget for native code is per-experience, shared across ALL native scripts. Use `--record-stats=function` to check each function's native instruction count and `debug.dumpcodesize()` in Studio to see per-experience usage. A few well-chosen `@native` functions outperform blanket `--!native` across many files.
Introduce a `const` keyword for local variable bindings that prevents reassignment after initialization. A const declaration makes the binding immutable, not the value, so tables and other mutable objects remain mutable through the binding.
## Motivation
When introducing the [`export`](https://github.com/luau-lang/rfcs/pull/42) keyword we will need to introduce the concept of `const`-ness to the language. Modules with exports return a frozen table of values; however, the variables used to declare those exports are not themselves immutable. This makes it possible to write code such as the following:
```luau
export foo = 5
export function increment()
foo += 1
end
```
This results in different values for `foo` depending on where it is accessed from:
```luau
-- Inside the module
print(foo) --> 5
increment()
print(foo) --> 6
-- Outside the module
print(module.foo) --> 5
module.increment()
print(module.foo) --> 5
```
To address this, we propose that all exported values are `const` by default, preventing reassignment within the module and ensuring that exported bindings represent stable values.
However, this is not the only use-case for `const`. A `const` binding provides a clear guarantee that a name will always refer to the same value for the lifetime of its scope. This property is useful beyond module exports: it makes code easier to reason about, avoids accidental rebinding in closures or long-lived scopes, and allows tools and the typechecker to treat such bindings as stable references rather than values that may change over time. Since `export` already requires this guarantee to behave correctly, exposing `const` as a general language feature avoids introducing special-case rules and provides a simple, consistent way to express immutability of bindings where it is desired.
## Design
### Syntax
Allow `const` in declarations anywhere `local` is allowed:
```luau
const x = 5
const f = function() end
const t = { a = 1 }
```
Typed declarations remain valid:
```luau
const x: number = 5
const t: { a: number } = { a = 1 }
```
Multi-assignment is also supported:
```luau
const a, b = 1, 2
```
A `function` can be declared as `const`:
```luau
const function f()
-- do something
end
```
### Semantics
A `const` variable is equivalent to a variable declared with `local` however `const` variables cannot be reassigned after they are initialized:
```luau
const x = 1
x = 2 -- error
```
This includes any assignment form that writes to the binding, such as compound assignment:
```luau
const x = 1
x += 1 -- error
```
It applies to the variable's binding, not to the value it refers to. If a const variable refers to a mutable value such as a table, that value can still be modified:
```luau
const t = { count = 0 }
t.count += 1 -- ok (mutating the table)
t = {} -- error (reassigning the binding)
```
`const` is not a replacement for `table.freeze` which should be used to prohibit mutation of table contents. The two features address different problems and can be used together:
```luau
const t = table.freeze({
count = 0,
})
t.count += 1 -- error (mutating a frozen table)
t = {} -- error (reassigning the binding)
```
#### Initialization
A `const` binding must be initialized at the point of declaration:
```
const x -- error: const variable must be initialized
```
This keeps the feature simple, avoids "definite assignment" rules, and makes the "never reassigned" intent explicit.
#### Scope and Shadowing
`const` follows normal lexical scoping rules. Shadowing creates a new binding and is allowed:
```luau
const x = 1
do
const x = 2 -- ok: new binding in inner scope
end
const y = 1
const y = 2 -- also ok: rebinding as const in same scope
```
#### Captures / Up-Values
If a `const` variable is captured by an inner function, it remains non-reassignable from within that function as well:
```luau
const x = 1
const function f()
x = 2 -- error
end
```
### Grammar / Parsing
`const` is a contextual keyword that is only valid in positions where `local` is valid. This makes the introduction fully backwards compatible with existing code.
## Drawbacks
- Potential false sense of immutability: developers may misread const as implying deep immutability. This is mitigated by clear documentation and examples, but the risk remains.
- Lua introduced `const` in the form `local foo <const> = 5` which is inconsistent with this syntax. However, we've already decided we won't adopt Lua const syntax, therefore consider this an acceptable change.
## Alternatives
- Lint-only "never reassigned" rule: A linter can infer locals that are never reassigned and warn on future changes. This avoids new syntax, but loses the ability to declare intent directly in code (e.g., a new assignment becomes a warning rather than a clear violation of an explicit constraint).
- Deep immutability / freezing: Enforcing immutability of tables (recursively) would address mutation bugs but introduces complexity, runtime overhead, questions around metatables/userdata, and awkward interactions with existing patterns. This is explicitly out of scope for this proposal.
- Annotations instead of a keyword: An attribute-like syntax (comment directive or type-level modifier) could avoid reserving a keyword, but is less readable, less discoverable, and harder to standardize across tooling.
- Do nothing: Continue relying on convention and review. This keeps the language smaller but preserves a common footgun and prevents tooling from enforcing a widely useful invariant.
@ -59,3 +59,165 @@ Standalone files in `roblox/en-us/`:
Use glob and grep to search `.cursor/rules/roblox/` for Roblox-specific documentation files.
**Tip:** Check `roblox/common/navigation/` for YAML files that organize documentation by category (analytics, avatar, cloud, engine, monetization, etc.). These navigation files help locate relevant docs quickly.
## Roblox Full API Dump
The file `.cursor/rules/roblox-api/Full-API-Dump.json` is the authoritative source for every Roblox API class, member, and enum. It is pulled directly from the [RobloxAPI/build-archive](https://github.com/RobloxAPI/build-archive) on each sync and reflects the current production build.
Find the result where the line above contains `"MemoryCategory":`. Read forward 6 lines to get `Superclass` and `Tags`. Note: the class's `Members` array begins thousands of lines above that line — large classes like `BasePart` span 2,500+ lines of members.
Find results where the line 1–3 lines above contains `"MemberType":` — those are member definitions. Use Read at that line ±15 lines to capture the full member object (Security, ThreadSafety, ValueType or Parameters/ReturnType, and Tags).
**3. Identify which class owns a member**
From the member's line number N, search backward through the file for the nearest `"MemoryCategory":` line. The `"Name":` on the very next line is the owning class name. Many common members (like `Anchored`) appear in multiple classes — check the owning class to confirm you have the right one.
**4. Check whether a member is defined on a class vs inherited**
Grep for the class Name to get its definition line number L (where `"MemoryCategory":` is on the line above). Then grep for the member Name and find the match whose line number is closest to and **below** L — that is the member as defined directly on that class.
**5. List all Functions available on a class**
```
rg -n '"MemberType": "Function"' .cursor/rules/roblox-api/Full-API-Dump.json -A 1
```
Each `-A 1` line is the function name. To scope to a specific class, find the class's definition line L from pattern 1, then find the previous class's definition line P. Members for the target class fall between lines P and L. Filter the `rg` output to that line range. Alternatively, use Read on the class's member block directly.
**6. List all Events available on a class**
```
rg -n '"MemberType": "Event"' .cursor/rules/roblox-api/Full-API-Dump.json -A 1
```
Same scoping approach as functions above.
**7. List all Properties available on a class**
```
rg -n '"MemberType": "Property"' .cursor/rules/roblox-api/Full-API-Dump.json -A 1
```
Same scoping approach. The `-A 1` line is always the property name since `"MemberType"` is always the line directly before `"Name"` in a member object.
**8. Look up an enum and all its values**
Enum names appear **after** their items in the file, so use `-B` to read upward:
Use the result in the Enums section (line 189,996+). Increase to `-B 300` for large enums.
**9. Find all direct subclasses of a class**
```
rg -n '"Superclass": "BasePart"' .cursor/rules/roblox-api/Full-API-Dump.json -A 1
```
The `-A 1` line after each match is the subclass `Name`.
### Cross-referencing the dump with the docs
The dump and the docs in `.cursor/rules/roblox/` are complementary — use either as the entry point:
- **Dump → Docs:** You confirmed a class or member exists in the dump (name, types, security level). Now search the docs to understand correct usage patterns, recommended approaches, and gotchas the API signature alone does not convey.
- **Docs → Dump:** The docs mention a class, function, or concept. Look it up in the dump to get the exact parameter types, return type, thread safety, security level, and deprecation status — detail the docs often omit or leave vague.
To find relevant docs after identifying a class or member:
- `Glob` into the relevant subdirectory: e.g. `roblox/en-us/physics/**` for physics, `roblox/en-us/scripting/**` for scripting patterns
- `Grep` across all docs: e.g. `rg "TweenService" .cursor/rules/roblox/` to find every file that discusses a class
- `roblox/en-us/reference/` contains YAML-format API reference files with additional context
- `roblox/common/navigation/` YAML files map topics to doc files — a fast way to find which doc covers a given system
The docs explain intent and usage; the dump is the ground truth for the API contract. Consult both before writing code against an unfamiliar API.
### Security levels
| Value | Accessible from |
|---|---|
| `"None"` | Any script |
| `"LocalUserSecurity"` | LocalScript only |
| `"PluginSecurity"` | Plugin scripts only |
| `"RobloxScriptSecurity"` | CoreScript / internal — **not accessible** from user scripts |
Security can be split per operation: `{ "Read": "None", "Write": "RobloxScriptSecurity" }` means readable but not writable from user scripts.
### Tags to check before using a member
| Tag | Meaning |
|---|---|
| `"Deprecated"` | Avoid — check for `"PreferredDescriptorName"` in the same Tags array for the replacement |
| `"NotCreatable"` | Cannot use `Instance.new()` — must obtain from the DataModel |
| `"Service"` | Obtain via `game:GetService("ServiceName")` |
| `"ReadOnly"` | Property cannot be set |
| `"NotReplicated"` | Changes do not sync across the client/server boundary |
### Large class warning
Classes like `BasePart`, `Instance`, and `Humanoid` have 500–2,500 lines of members. Never attempt to read an entire class block. Always grep for the specific member, function, or event you need.
@ -58,8 +58,8 @@ To create a custom Roblox accessory for your own experience or to sell on the Ma
- [Clothing creation tutorial](../../art/accessories/creating/index.md) - a step-by-step process of creating your own avatar-ready clothing from scratch in Blender.
- [Additional tools, resources, and guides](#resources) provided by Roblox to standardize and expedite the creation process.
<Alertseverity ='info'>
If you are a developer and are looking for information on manually equipping or configuring layered clothing accessories within an experience, including implementing layered clothing on a non-standard character model, see [Character appearance](../../characters/appearance.md).
<Alertseverity ='warning'>
If you are a developer and are looking for information on manually equipping or configuring layered clothing accessories within an experience, including [implementing layered clothing on a non-standard character model](../../characters/appearance.md#layered-clothing-on-non-r15), see [Character appearance](../../characters/appearance.md).
</Alert>
Roblox also supports [classic clothing](#classic-clothing), 2D images that can be applied to your character's surface.
<tr><td>**And then we'll take over the world!**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/And_Then_Well_Take_Over_The_World_12732366.png"/></td></tr>
<tr><td>**And then we'll take over the world!**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/AndThenWellTakeOverTheWorld_12732366.png"/></td></tr>
<tr><td>**Astral Isle Clan: Windsor the Blue - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/AstralIsleClanWindsorTheBlueFace_2627730985.png"/></td></tr>
<tr><td>**Big Grin - Tai Verdes**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/BigGrinTaiVerdes_7987180607.png"/></td></tr>
<tr><td>**Big Sad Eyes**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/BigSadEyes_391496223.png"/></td></tr>
<tr><td>**Biohazard First Responder - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/BiohazardFirstResponderFace_4927805902.png"/></td></tr>
<tr><td>**Bird of Prey**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/BirdOfPrey_10907547.png"/></td></tr>
<tr><td>**Butterfly Wink - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Butterfly_Wink_Lil_Nas_XLNX_7657646351.png"/></td></tr>
<tr><td>**Cat Eye - Zara Larsson**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Cat_Eye_Zara_Larsson_7893455671.png"/></td></tr>
<tr><td>**Champion Of The Tide Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/ChampionOfTheTideFace_3381167752.png"/></td></tr>
<tr><td>**Chichiri the Wise Youkai Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/ChichiriTheWiseYoukaiFace_3210252812.png"/></td></tr>
<tr><td>**Devil Nas X - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Devil_Nas_X_Lil_Nas_XLNX_7657657626.png"/></td></tr>
<tr><td>**Diamond Grill - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Diamond_Grill_Lil_Nas_XLNX_7657627637.png"/></td></tr>
<tr><td>**Dark Age Apprentice - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/DarkAgeApprenticeFace_2510119972.png"/></td></tr>
<tr><td>**Devil Nas X - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/DevilNasXLilNasXLNX_7657657626.png"/></td></tr>
<tr><td>**Diamond Grill - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/DiamondGrillLilNasXLNX_7657627637.png"/></td></tr>
<tr><td>**Digital Artist - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/DigitalArtistFace_2936946480.png"/></td></tr>
<tr><td>**Digital Artist - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/DigitalShockArtistFace_2936946480.png"/></td></tr>
<tr><td>**Egg on your Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/EggOnYourFace_110207471.png"/></td></tr>
<tr><td>**Eita the Envious Youkai Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/EitaTheEnviousYoukaiFace_3210252000.png"/></td></tr>
<tr><td>**Elf Guardian of the Northern Border - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/ElfGuardianOfTheNorthernBorderFace_2510329394.png"/></td></tr>
<tr><td>**Glittering Eye - Zara Larsson**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Glittering_Eye_Zara_Larsson_7893460486.png"/></td></tr>
<tr><td>**Glory on the Gridiron**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Glory_On_The_Gridiron_21802396.png"/></td></tr>
<tr><td>**Heart Gaze - Zara Larsson**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/HeartGazeZaraLarsson_7893468574.png"/></td></tr>
<tr><td>**I <3 New Site Theme**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/I3_New_Site_Theme_32723404.png"/></td></tr>
<tr><td>**I Am Not Amused**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/I_Am_Not_Amused_7131886.png"/></td></tr>
<tr><td>**I Didn't Eat That Cookie**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/I_Didnt_Eat_That_Cookie_116042990.png"/></td></tr>
<tr><td>**I Hate Noobs**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/I_Hate_Noobs_14030577.png"/></td></tr>
<tr><td>**I <3 New Site Theme**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/I3NewSiteTheme_32723404.png"/></td></tr>
<tr><td>**I Am Not Amused**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/IAmNotAmused_7131886.png"/></td></tr>
<tr><td>**I Didn't Eat That Cookie**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/IDidntEatThatCookie_116042990.png"/></td></tr>
<tr><td>**I Hate Noobs**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/IHateNoobs_14030577.png"/></td></tr>
<tr><td>**I Lack Personal Confidence**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/ILackPersonalConfidence_7506144.png"/></td></tr>
<tr><td>**Jacob: The Storm Breaker Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/JacobTheStormBreakerFace_599913343.png"/></td></tr>
<tr><td>**Knight of Chivalry - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/KnightOfChivalryFace_2510299324.png"/></td></tr>
<tr><td>**Knight of Courage - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/KnightofCourageFace_2535711033.png"/></td></tr>
<tr><td>**Knight of Splintered Skies Ascendant - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/KnightOfSplinteredSkiesAscendantFace_2506721000.png"/></td></tr>
<tr><td>**Neoclassic Male v2 - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Neoclassic_Malev2_Face_4637291815.png"/></td></tr>
<tr><td>**Nibbles, Devourer of Worlds**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/NibblesDevourerOfWorlds_11913734.png"/></td></tr>
<tr><td>**Noriko the Gentle Youkai Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/NorikoTheGentleYoukaiFace_3210253450.png"/></td></tr>
<tr><td>**Not So Friendly Eviscerator**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/NotSoFriendlyEviscerator_66330204.png"/></td></tr>
<tr><td>**Rodeo Vampire - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Rodeo_Vampire_Lil_Nas_Xlnx_5917448278.png"/></td></tr>
<tr><td>**Rogueish Good Looks**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Rogueish_Good_Looks_1665511048.png"/></td></tr>
<tr><td>**Rock Star Singer - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/RockStarSingerFace_2517188619.png"/></td></tr>
<tr><td>**Rodeo Vampire - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/RodeoVampireLilNasXlnx_5917448278.png"/></td></tr>
<tr><td>**Rogue Era Magus - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/RogueEraMagusFace_2499710871.png"/></td></tr>
<tr><td>**Rogueish Good Looks**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/RogueishGoodLooks_1665511048.png"/></td></tr>
<tr><td>**Sai-eye Tyler Joseph - Twenty One Pilots**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SaieyeTylerJosephTwentyOnePilots_7389910081.png"/></td></tr>
<tr><td>**Sharpnine's Face of Disappointment**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Sharpnines_Face_Of_Disappointment_209994270.png"/></td></tr>
<tr><td>**Sharpnine's Face of Disappointment**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SharpninesFaceOfDisappointment_209994270.png"/></td></tr>
<tr><td>**Sharpnine's Face of Joy**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SharpninesFaceOfJoy_406002032.png"/></td></tr>
<tr><td>**Sly Guy Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Sly_Guy_Face_238983378.png"/></td></tr>
<tr><td>**Smil Nas X - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Smil_Nas_X_Lil_Nas_XLNX_5917459717.png"/></td></tr>
<tr><td>**Sly Guy Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SlyGuyFace_238983378.png"/></td></tr>
<tr><td>**Smil Nas X - Lil Nas X (LNX)**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SmilNasXLilNasXLNX_5917459717.png"/></td></tr>
<tr><td>**So Super Excited - Blue**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/So_Super_Excited_Blue_2569001052.png"/></td></tr>
<tr><td>**So Super Excited - Pink**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/So_Super_Excited_Pink_2568801198.png"/></td></tr>
<tr><td>**Sparkle Time Sparkle Eyes**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Sparkle_Time_Sparkle_Eyes_2620507161.png"/></td></tr>
<tr><td>**So Super Excited - Blue**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SoSuperExcitedBlue_2569001052.png"/></td></tr>
<tr><td>**So Super Excited - Pink**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SoSuperExcitedPink_2568801198.png"/></td></tr>
<tr><td>**So Super Excited - Purple**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SoSuperExcitedPurple_2568804274.png"/></td></tr>
<tr><td>**Sorority Star Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SororityStarFace_3690515434.png"/></td></tr>
<tr><td>**Sparkle Time Sparkle Eyes**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SparkleTimeSparkleEyes_2620507161.png"/></td></tr>
<tr><td>**Sunrise Eyes - Tai Verdes**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Sunrise_Eyes_Tai_Verdes_7987201392.png"/></td></tr>
<tr><td>**Sunrise Eyes - Tai Verdes**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/SunriseEyesTaiVerdes_7987201392.png"/></td></tr>
<tr><td>**Tohru: The Phantom Claw's Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Tohru_The_Phantom_Claws_Face_1384270693.png"/></td></tr>
<tr><td>**Too Much Candy**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Too_Much_Candy_295763789.png"/></td></tr>
<tr><td>**The Dog Whisperer**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/TheDogWhisperer_34764447.png"/></td></tr>
<tr><td>**The First Time I Ever Played ROBLOX...**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/TheFirstTimeIEverPlayedRoblox_22070623.png"/></td></tr>
<tr><td>**Up To Something**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/UpToSomething_1016186364.png"/></td></tr>
<tr><td>**Upside Down Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/UpsideDownFace_1772578399.png"/></td></tr>
<tr><td>**Valkyrie of the Splintered Skies - Face**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/ValkyrieOfTheSplinteredSkiesFace_2907632781.png"/></td></tr>
<tr><td>**Vans Checkerboard Blue Eyes**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Vans_Checkerboard_Blue_Eyes_8247356154.png"/></td></tr>
<tr><td>**Visual Studio Seized Up For 45 Seconds... Again**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Visual_Studio_Seized_Up_For45_Seconds_Again_14083380.png"/></td></tr>
<tr><td>**Walk the Plank You Scurvy Dogs!**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/Walk_The_Plank_You_Scurvy_Dogs_13478124.png"/></td></tr>
<tr><td>**Vans Checkerboard Blue Eyes**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/VansCheckerboardBlueEyes_8247356154.png"/></td></tr>
<tr><td>**Visual Studio Seized Up For 45 Seconds... Again**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/VisualStudioSeizedUpFor45SecondsAgain_14083380.png"/></td></tr>
<tr><td>**Walk the Plank You Scurvy Dogs!**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/WalkThePlankYouScurvyDogs_13478124.png"/></td></tr>
<tr><td>**Warpaint Josh Dun - Twenty One Pilots**</td><tdcolspan="2"><imgsrc="../../assets/art/avatar/classic-head/WarpaintJoshDunTwentyOnePilots_7389923463.png"/></td></tr>
@ -7,6 +7,9 @@ description: In-depth details on how Roblox validates head assets
This content is for creators who intend to create character heads designated for the Marketplace, or any character heads that support assets from the Marketplace.
</Alert>
<iframewidth="800"height="450"src="https://www.youtube-nocookie.com/embed/OwhkWzSBnf0"title="YouTube video player"frameborder="0"allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture"allowFullScreen></iframe>
<br/><br/>
Roblox runs a rigorous validation to ensure the asset being uploaded to the Marketplace is compatible with all other assets on the Marketplace. For heads, validation primarily ensures that the head asset includes:
- A correctly **configured cage** — to wear clothing and accessories.
description: Instructions for creators who need to re-upload their head for validation.
---
<Alertseverity ='warning'>
The following content applies to **creators with head assets on the Marketplace** who received a notice that their head assets **no longer pass dynamic head validation.**
</Alert>
<Alertseverity ='info'>
This document is intended to be updated frequently to include the latest information to help guide you through the head update process.
</Alert>
If your head asset no longer passes the dynamic head validation, you need to fix and reupload your head asset to the Marketplace. **You do not need to update the body when the head was published as part of a body bundle**. You just need to update the head asset.
This guide provides detailed information on how to perform a manual fix by re-caging your asset using a provided cage template using the following 5 main steps:
1. [Re-validate](#1-verify-the-catalog-head-asset-fails-validation) your catalog head asset to confirm issues.
2. [Export your head](#2-export-your-head-from-roblox) (or body bundle) from Roblox as a glTF.
3. [Fix your Head](#3-fix-your-head-asset-in-blender) asset in Blender.
4. [Import](#4-import-your-fixed-head-back-into-studio) your fixed head back into Studio.
5. [Provide the asset ID](#5-provide-the-assetid-of-your-updated-head) of your updated head to Roblox.
<Alertseverity ='info'>
While these instructions are provided for Blender, it's possible to perform equivalent steps in your preferred 3D modeling software. It's recommended to familiarize yourself with Roblox's head validation process before making changes to your head assets.
</Alert>
## 1. Verify the catalog head asset fails validation
Roblox recently updated the dynamic head validation parameters to allow more existing catalog heads to pass validation while still enforcing that all heads in the catalog are dynamic. It's important to double check that your head still fails validation before proceeding with the rest of the steps.
1. Open a new Roblox Studio and verify you have the beta feature **glTF Export** enabled (you might need to restart Studio to pick up the new setting)
1. Verify your beta settings in **File** > **Beta Features**.
2. Run the following script in the command bar.
1. You have the option to provide either the head id (asset ID) or the bundle id (for a full body bundle) . Just replace `my_asset_id` with the correct ID and remove the leading `--` to run
2. Uncomment `createExportFromHeadAssetId(your_asset_id)` to retrieve only the head asset
3. Uncomment `createExportFromBodyBundleId(your_bundle_id)` to retrieve the full body as a bundle (includes the head)
```lua
local AssetService = game:GetService("AssetService")
local Players = game:GetService("Players")
local function createExportFromHeadAssetId(assetId: number)
local hd = Instance.new("HumanoidDescription")
hd.Head = assetId
local char = Players:CreateHumanoidModelFromDescriptionAsync(hd, Enum.HumanoidRigType.R15)
local headMesh = char.Head :: MeshPart
headMesh.Size = headMesh.MeshSize
headMesh.CFrame = CFrame.identity
headMesh.Locked = false
local exportModel = Instance.new("Model", workspace)
exportModel.Name = "ExportThisAsGLTF"
headMesh.Parent = exportModel
end
local function getOutfitId(bundleInfo)
for _, item in pairs(bundleInfo.Items) do
if item.Type == "UserOutfit" then
return item.Id
end
end
error("Could not find outfit ID")
end
local function createExportFromBodyBundleId(bundleId: number)
local bundleInfo = AssetService:GetBundleDetailsAsync(bundleId)
local outfitId = getOutfitId(bundleInfo)
local hd = Players:GetHumanoidDescriptionFromOutfitIdAsync(outfitId)
local char = Players:CreateHumanoidModelFromDescriptionAsync(hd, Enum.HumanoidRigType.R15)
char:PivotTo(CFrame.identity)
char.Name = "ExportThisAsGLTF"
char.Parent = workspace
end
-- createExportFromHeadAssetId(your_asset_id)
-- createExportFromBodyBundleId(your_bundle_id)
```
3. Once the script runs, you will see a "ExportThisAsGLTF" model in workspace. Right-click and select **Save To Roblox**. This will popup the publish window:
1. In Content type, select **Avatar Item**.
2. In Asset Category, select **Head or Body** (if you used a body bundle).
1. Wait for the validation result.
3. If the dynamic head validation passes then you don't have to update this asset.
4. **Cancel** the publish operation (so you don't incur fees).
If your asset failed the dynamic head validation then you need to continue with the following steps.
## 2. Export Your Head from Roblox
Export your head or entire body from Blender as a glTF file:
1. In the explorer, right-click on the "ExportThisAsGLTF" model and select **Save/Export** > **Save as glTF**.
## 3. Fix your Head asset in Blender
Fix your asset in Blender, or preferred 3D modeling software. In many cases, you may save time using a brand new head cage and fitting the cage over your character head. You may also see other validation issues that need to be addressed from the first step.
1. In Blender, open a new empty scene and import the glTF file.
2. Ensure everything you need for the head is present, including the cage, attachment points, facial joints and facial animations.
3. Roblox Studio flips avatars by 180 degrees on import, so you need to rotate your asset back by 180 in Blender (Hotkeys: <kbd>R</kbd>+<kbd>Z</kbd> then type `180`).
4. Most often heads fail validation due to cage issues. For example, the cage may be completely inside the head, has incorrect UVs or looks completely misplaced in the model. In most cases we recommend you do not spend time editing the original cage and instead:
1. Delete it from the asset.
2. Select the cage from the head template in this folder that best matches the shape of your head asset. These are [Roblox provided templates for cages with the correct UV settings](../../assets/art/reference-files/HeadCages.zip).
5. Ensure the Blender animation start value is set to 0.
6. Remove vertex colors. In Blender this can be done by clicking on the mesh and then under the data tab selecting color attributes and removing any that exist.
2. Ensure your head includes a rig, with poses keyframed and mapped to the FACs standard so that:
1. Eyes must blink.
2. Mouth must open and close.
3. Face must express happy and sad.
8. Export your updated head asset to FBX file format using the correct settings explained here (If you have PBR, you may want to use glTF).
1. Setup textures for FBX export (if needed). Blender will import the glTF textures in such a way that they won't be able to be exported in an FBX file by default.
1. To fix this go through each image and save it to your disk locally.
Import your head back into Studio and verify that it passes validation. If validation fails, check the error messages and address the issues in your 3D modeling software again. If validation passes, use the following steps to upload as a **Development Item** (not Avatar item).
1. Import the FBX/glTF file with your (fixed) head asset back into Studio.
2. Make sure your head asset passes validation.
1. The easiest way to accomplish this is:
1. Select **Save to Roblox** on your asset.
2. Select **Avatar Item**.
3. Set its **Asset Category** to Head.
4. Wait for the Dynamic Head validation result (pass or fail).
5. **Cancel** the save operation so you don't incur publishing fees at this time.
2. If your head doesn't pass validation then you need to go back to Blender, Maya and make the appropriate fixes to pass validation.
3. If your head passes validation, publish your asset as a Development Item.
1. Select **Save to Roblox** on your asset.
2. Select **Development Item**.
3. Set its **Asset Category** to Model.
4. Save the model to Roblox (no publishing fees are incurred).
1. Get the asset ID of the head asset (not the body bundle).
## 5. Provide the assetID of your updated head
Use the following steps to provide Roblox the updated asset ID to replace your head with:
1. Use the link to the web form in the email notification corresponding to your asset to communicate back the asset ID of your updated head.
1. You will be asked to enter your date of birth for each update. This is a legal requirement we can't bypass.
2. You can submit multiple times. We will use your last submission as the "final" version to the update.
2. Roblox will complete the update of your catalog asset with the modifications you provided.
## Additional Resources
Roblox has published a series of documentation and video tutorials on best practices for creating dynamic head assets.
description: The Avatar Validation Tool checks for common character model issues in Blender.
---
<Alertseverity ='warning'>
This tool is no longer in active development. You are free to use the tool for any purpose. The tool may be out of date or incompatible with modern Roblox assets.
</Alert>
The **Avatar Validation Tool** is a supplemental Blender add-on you can use to quickly identify common issues with your character model. This tool checks that your model meets most of Roblox's avatar [technical specifications](../../art/characters/specifications.md) and, when possible, attempts to automatically resolve them.
@ -17,7 +21,7 @@ The Validation Tool plugin is compatible with Blender 3.5.1+. You may experience
To install the Validation Tool in Blender:
1. Download [ValidationTool_Blender.zip](../../assets/art/ValidationTool_V2.zip) and save the .zip file locally. Do not open the .zip file.
1. Download [ValidationTool_Blender.zip](https://github.com/Roblox/avatar/tree/main/Tools/ClothingValidationTool) and save the .zip file locally. Do not open the .zip file.
2. In Blender, navigate to **Edit** > **Preferences**.
3. In Preferences, go to the **Add-Ons** section on the left sidebar.
description: Calisthenics Tool is a Blender plugin that you can use to verify skinning quality of an asset.
---
<Alertseverity ='warning'>
This tool is no longer in active development. You are free to use the tool for any purpose. The tool may be out of date or incompatible with modern Roblox assets.
</Alert>
The **Calisthenics Tool** is a supplemental [Blender](https://www.blender.org/) add-on that allows you to quickly test your asset through a set of animation cycles to verify your skinning data. At any point during the animation testing, you can pause and use Blender's skinning Tools, such as [Weight Painting brushes](https://docs.blender.org/manual/en/latest/sculpt_paint/weight_paint/introduction.html), to resolve any skinning imperfections.
Skinning your clothing and characters is a critical and often time-intensive process to create high quality assets that move and fit with different character bodies. Similar to the [Layered Clothing Validation Tool](../../art/accessories/validation-tool.md), the Calisthenics Tool can save you time when testing your character models after rigging and skinning.
@ -34,7 +38,7 @@ The Calisthenics Tool plugin is designed for [Blender 3.0+](https://www.blender.
To install the Calisthenics Tool in Blender:
1. Download the [CalisthenicsTool.zip](../../assets/modeling/skinned-meshes/calisthenic-tool/CalisthenicsTool.zip) and save the `.zip` file locally.
1. Download the [CalisthenicsTool.zip](https://github.com/Roblox/avatar/tree/main/Tools/CalisthenicsTool) and save the `.zip` file locally.
2. In Blender, navigate to **Edit** > **Preferences**.