Use when building or generating a Roblox map, level, obby, house or prop out of parts from code or Studio - to avoid z-fighting, overlapping or intersecting parts, unanchored geometry, wasted collisions, bad mesh collision, and fake stud textures. Covers Anchored, CanCollide/CanQuery/CanTouch, CollisionFidelity (including Tunable), collision groups, surfaces (SurfaceType.Studs) and stud MaterialVariants.
Use the robloxvibecoders MCP: call get_resource with id "skill-roblox-build-quality" and install it as a Claude Code skill in this project (.claude/skills/roblox-build-quality/SKILL.md).
Skills in ~/.claude/skills work in every project. Put them in your project's .claude/skills folder to share them with your team.
What your AI reads
SKILL.md
---
name: roblox-build-quality
description: Use when building or generating a Roblox map, level, obby, house or prop out of parts from code or Studio - to avoid z-fighting, overlapping or intersecting parts, unanchored geometry, wasted collisions, bad mesh collision, and fake stud textures. Covers Anchored, CanCollide/CanQuery/CanTouch, CollisionFidelity (including Tunable), collision groups, surfaces (SurfaceType.Studs) and stud MaterialVariants.
---
# Clean builds for AI-made maps
AI-generated maps usually fail in the same ways: flickering faces, parts poking through each other, things that
fall when the game starts, and decorations that block players. Follow these rules for every part you place.
## 1. No z-fighting
Z-fighting is the flicker you see when two faces lie in the **same plane and overlap** (a floor tile sitting
exactly on a floor, a door flush inside a wall, two walls sharing a face). The renderer can't decide which is in
front.
- Never stack two visible faces in the same plane. Either merge them into one part, cut the base part so the pieces
sit side by side, or offset the top piece by **0.01 to 0.05 studs** (more for big surfaces seen from far away).
- A trim, rug or road marking sits on top of the surface: `y = surfaceTop + thickness / 2 + 0.02`.
- Inset details (windows, doors) must be thinner than the wall or stick out of both sides, never flush.
- For flat details (signs, stains, posters) use a `Decal` on the part's `Face` instead of a paper-thin part. Several
decals on the same face are ordered with `Decal.ZIndex`.
## 2. No accidental overlaps
Parts should touch, not intersect, unless the intersection is intended and hidden.
- Snap: put positions and sizes on a grid (1 stud for structure, 0.5 or 0.25 for detail) and keep consistent
thicknesses (for example every wall 1 stud thick, every floor 1 stud).
- Walls meeting at a corner: one wall runs the full length, the other stops at its inside face (length minus
thickness). Don't let both run to the corner.
- Work out every part from its bounds: `top = cf.Y + size.Y / 2`. For models use `Model:GetBoundingBox()` (CFrame,
size) or `Model:GetExtentsSize()`, and place with `Model:PivotTo()`.
- Check what is already there before placing: `workspace:GetPartBoundsInBox(cframe, size, overlapParams)` or
`workspace:GetPartsInPart(part, overlapParams)`. Parts with `CanQuery = false` never show up in these queries.
Audit script (Studio command bar) for axis-aligned block parts:
```luau
local root = workspace.Map
local EPS = 0.001
local function bounds(part: BasePart): (Vector3?, Vector3?)
local cf = part.CFrame
for _, axis in { cf.RightVector, cf.UpVector, cf.LookVector } do
if math.abs(math.max(math.abs(axis.X), math.abs(axis.Y), math.abs(axis.Z)) - 1) > EPS then
return nil, nil -- rotated at an odd angle, skip
end
end
local h = cf:VectorToWorldSpace(part.Size / 2)
h = Vector3.new(math.abs(h.X), math.abs(h.Y), math.abs(h.Z))
return cf.Position - h, cf.Position + h
end
local boxes = {}
for _, d in root:GetDescendants() do
if d:IsA("Part") and d.Shape == Enum.PartType.Block and d.Transparency < 1 then
local lo, hi = bounds(d)
if lo and hi then
table.insert(boxes, { part = d, lo = lo, hi = hi })
end
end
end
local function overlap(a0: number, a1: number, b0: number, b1: number): number
return math.min(a1, b1) - math.max(a0, b0) -- > 0 when the ranges overlap
end
-- O(n^2): fine for a few thousand parts. Split the map into folders beyond that.
for i = 1, #boxes do
local A = boxes[i]
for j = i + 1, #boxes do
local B = boxes[j]
local ox = overlap(A.lo.X, A.hi.X, B.lo.X, B.hi.X)
local oy = overlap(A.lo.Y, A.hi.Y, B.lo.Y, B.hi.Y)
local oz = overlap(A.lo.Z, A.hi.Z, B.lo.Z, B.hi.Z)
if ox > 0.01 and oy > 0.01 and oz > 0.01 then
warn("Intersecting:", A.part:GetFullName(), B.part:GetFullName())
end
local sameTop = math.abs(A.hi.Y - B.hi.Y) < EPS or math.abs(A.lo.Y - B.lo.Y) < EPS
local sameX = math.abs(A.hi.X - B.hi.X) < EPS or math.abs(A.lo.X - B.lo.X) < EPS
local sameZ = math.abs(A.hi.Z - B.hi.Z) < EPS or math.abs(A.lo.Z - B.lo.Z) < EPS
if (sameTop and ox > 0 and oz > 0) or (sameX and oy > 0 and oz > 0) or (sameZ and ox > 0 and oy > 0) then
warn("Coplanar faces (z-fighting risk):", A.part:GetFullName(), B.part:GetFullName())
end
end
end
```
## 3. Anchor everything static
Every part of the map that should not move gets `Anchored = true`. Unanchored map parts fall, jitter, get pushed
by players and cost physics time. Only unanchor things that are meant to be physical (doors on hinges, crates,
vehicles), and weld their pieces together.
## 4. Turn off collision work for decoration
| Property | Set false for | Effect |
|---|---|---|
| `CanCollide` | grass tufts, leaves, small trim, light beams, particles' host parts | players and parts pass through |
| `CanTouch` | anything without a `Touched` handler | no touch events to compute |
| `CanQuery` | decoration that raycasts should ignore (only works when `CanCollide` is false) | invisible to raycasts and spatial queries |
| `CastShadow` | tiny details, glowing parts | fewer shadow casters |
| `AudioCanCollide` | foliage, thin decorations | sound passes through (Audio API acoustic simulation) |
Invisible walls and kill bricks keep `CanCollide` or `CanTouch` on as needed, `Transparency = 1`.
## 5. Mesh and union collision
`CollisionFidelity` on MeshParts and unions decides the collision shape. Set it in Studio:
- `Box`: small props players don't climb (bottles, books, plants). Cheapest.
- `Hull`: convex-ish props (rocks, barrels, crates).
- `Default`: general use.
- `PreciseConvexDecomposition`: things players walk inside or on (arches, stairs, hollow shapes). Most expensive.
- `Tunable` (new in September 2026): exposes `CollisionPrecision` from 0 (fastest) to 1 (closest to the visual
mesh). The default precision depends on the mesh size, so size the mesh first. It is set in Studio (scripts can
read it, only plugins can write it).
Decoration meshes with `CanCollide = false` don't need precise collision at all.
## 6. Collision groups
Use collision groups instead of toggling `CanCollide` for "players pass through each other", "NPCs ignore doors"
and similar rules:
```luau
-- Server Script
local PhysicsService = game:GetService("PhysicsService")
PhysicsService:RegisterCollisionGroup("Players")
PhysicsService:RegisterCollisionGroup("Doors")
PhysicsService:CollisionGroupSetCollidable("Players", "Players", false)
for _, part in workspace.Doors:GetDescendants() do
if part:IsA("BasePart") then
part.CollisionGroup = "Doors"
end
end
```
`BasePart.CollisionGroup` is a string. `CreateCollisionGroup`, `SetPartCollisionGroup` and `CollisionGroupId` are
deprecated.
## 7. Studs the right way
Never fake studs with stud images in `Decal` or `Texture` objects: decals stretch with the part, textures need one
instance per face, and both add instances and texture memory.
**Classic studs on blocks:** use the part's surfaces. Surfaces are visual only now (surface joining is deprecated).
```luau
part.TopSurface = Enum.SurfaceType.Studs
part.BottomSurface = Enum.SurfaceType.Inlet
-- Smooth is the default; Universal mixes studs and inlets
```
**Studs on any shape (meshes, unions, terrain-like builds) or full control:** make one stud `MaterialVariant` in
`MaterialService` (in Studio: base material Plastic, a stud color map and normal map, `StudsPerTile` = how many
studs one texture tile covers, for example 1 for a single-stud image) and apply it by name:
```luau
part.Material = Enum.Material.Plastic -- must match the variant's BaseMaterial
part.MaterialVariant = "Studs"
```
A variant's texture maps can only be set in Studio or by plugins, so create it while building, not at runtime.
## Mistakes to avoid
- Flush, overlapping faces anywhere (floors on floors, doors in walls, double walls at corners).
- Parts generated with random float sizes and positions. Round to the grid.
- Leaving map parts unanchored, or anchoring parts that are supposed to be physical.
- Every leaf and pebble with `CanCollide`, `CanTouch` and shadows on.
- `PreciseConvexDecomposition` on hundreds of small props.
- Thin decal-holder parts instead of decals on the real surface.
- Stud images in decals, or a different stud texture per part instead of one shared MaterialVariant.