The site is under maintenance, so logging in is temporarily unavailable. All resources and the MCP still work.

AI Skills

Clean builds for AI-made maps

By Roblox Vibecoders · roblox-build-quality

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.

Install it

Terminal
mkdir -p ~/.claude/skills/roblox-build-quality && curl -fsSL https://robloxvibecoders.com/skills/roblox-build-quality/SKILL.md -o ~/.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.