Games / Roblox

AI-player-ready Roblox games

Who it's for. You build a Roblox place the NPC should play (obby, paths, aiming, or your own).

What you'll do. Name parts, kill on lava, publish JumpNow on movers, then ship PlayBatch/PlayRuntime — never policy_weights.

Download the live pack: SDK · API. The NPC does not read your game's internal data. It sees only what its rays hit — classified by part names — then the Hub returns move / jump / fire. Web companion · Licensing · Deploy & compete

Making your game AI-ready means recognized naming, walkable physics, jumpable scale, a reachable goal, reliable death detection, and a spawn. Live compete places ship PlayRuntime + VisionSystem only — never policy_weights. Campus Train my AI player is the training exception.

1. Part naming conventions

The classifier reads part names. Without recognized prefixes, everything is an unknown obstacle.

RoleName patternExamples
PlatformPlatform_*, Floor*, SpawnPlatform_1, Floor_Main, Spawn
HazardHazard_*, Lava*Hazard_Lava_1, LavaFloor
RewardCoin_*, Reward_*Coin_1, Reward_Gem
GoalFinishLine, Finish, ExitFinishLine, Exit
WallWall_*Wall_North, Wall_1
TargetTarget_*Target_1, Target_Boss
Track / rampTrack_*, Ramp_*Classified as platform (truck / ramps)
Smash / slow / bounceSmash_*, Slow_*, Bounce_*Classified as hazard
MoverPlatform_* + KitMove (preferred)MovingPad_1 is not a vision prefix — use JumpNow overlay

Color fallback if the name is unknown: bright red → hazard, yellow → reward. Vision uses RespectCanCollide = false so lava is still seen. Coins: CanCollide = false, CanQuery = true. HRP does not collide; limbs do.

2. Physics setup

ObjectCanCollideWhy
PlatformstrueNPC must stand on them
Hazards (lava, pits)falseNPC falls through; Touched kills it
Rewards (coins)falseNPC walks through to collect
WallstrueNPC must not pass through

If lava is solid, the NPC stands on it instead of dying. Always CanCollide = false plus a kill handler.

-- ServerScriptService: name platforms and set collision
local function tagPlatform(part, index)
	part.Name = "Platform_" .. index
	part.CanCollide = true
	part.Anchored = true
end

local function tagWall(part, name)
	part.Name = "Wall_" .. name
	part.CanCollide = true
	part.Anchored = true
end

local function tagCoin(part, index)
	part.Name = "Coin_" .. index
	part.CanCollide = false
	part.CanQuery = true -- still visible to vision rays
end

3. Scale guidelines

PropertyLimitRationale
Max gap distance12 studs (hard ceiling)JumpPower 70 ceiling — do not train as default
Recommended learn gap2–4 studs (advanced ≤ 8)Training gyms in this project
Max height difference5 studs~5 stud jump-up reliably
Min platform size3×3 studsSmaller pads are hard to land on
WalkSpeed16–24Panic sprint is animation, not a new walk speed
UseGapHeight ΔJumpPowerWalkSpeed
Training gyms2–4 studs0–3~5016–24
Hard ceiling~8 studs~5 with boosthigher—
Do not promise12 as default Humanoid—JumpPower 70 as default—

Maze corridors ≥ 4 studs. Surge-style lanes: player radius 0.7, clearance ≥ 0.5, no two adjacent bumper lanes.

4. Path reachability

The goal must be reachable from spawn via safe zones. Place yourself at spawn and try to finish. If you cannot, the AI cannot either.

5. Death detection (Lua)

Option A — Touched (recommended)

local function killOnTouch(lavaPart)
	lavaPart.Name = "Hazard_Lava_1"
	lavaPart.CanCollide = false
	lavaPart.Size = Vector3.new(lavaPart.Size.X, 1, lavaPart.Size.Z) -- 1 stud tall
	lavaPart.Touched:Connect(function(hit)
		local char = hit:FindFirstAncestorWhichIsA("Model")
		if not char then
			return
		end
		local humanoid = char:FindFirstChildOfClass("Humanoid")
		if humanoid then
			humanoid.Health = 0
		end
	end)
end

Option B — Y + bounds in the server loop

-- In your 0.1s tick, after encoding obs — do not wait on HTTP
if hrp.Position.Y < 2.5 and isInHazardBounds(hrp.Position) then
	humanoid.Health = 0
end

Common pitfalls:

-- One-time world cleanup for a gym (server)
if workspace:FindFirstChild("Baseplate") then
	workspace.Baseplate:Destroy()
end
workspace.Terrain:Clear()

6. Spawn point

local spawnPart = workspace:FindFirstChild("Spawn") or workspace:FindFirstChildWhichIsA("SpawnLocation")
assert(spawnPart, "Name a Spawn part or add a SpawnLocation")
npc:PivotTo(CFrame.new(spawnPart.Position + Vector3.new(0, 3, 0)))

7. Vision config (optional)

Tune FOV for mazes, range for open worlds, edge probes for precision platforming.

-- TrainConfig.lua (campus) or a ModuleScript next to PlayRuntime (live)
local TrainConfig = {}
TrainConfig.VISION = {
	range = 30, -- max ray distance in studs
	origin_offset_y = 1.5, -- eye level above HRP
	forward_yaw = 180, -- +Z forward
	yaw_angles = { -50, -30, -10, 10, 30, 50 },
	pitch_angles = { -20, 0, 20 },
	edge_distances = { 2, 4, 6 },
	ground_yaw = { -30, 0, 30 },
}
return TrainConfig

Compatibility checker

Studio command bar

local Checker = require(game.ReplicatedStorage.AiarenaSdk.CompatibilityChecker)
Checker.report(workspace)

SDK (automated)

local result = CompatibilityChecker.check(workspace)
-- result.compatible = true | false
-- result.score = 0-100
-- result.checks = { { name, status, issues, fix_hint }, ... }
-- result.summary = "human-readable summary"
print(result.summary)
StatusMeaning
[PASS]Requirement met
[WARN]Issue found but the game may still be playable
[FAIL]Critical — not AI-ready

Live play: PlayRuntime (Lua)

Preferred: download aiarena-sdk-play.zip and install under ReplicatedStorage. Or copy from packages/aiarena-sdk/src/ (PlayRuntime, HttpBridge, PlayBatch). API shapes: /developers/api. Put the aig_ license in ServerStorage, never a LocalScript. Encode obs on the server; do not yield the physics loop on HTTP. Prefer think interval 0.15–0.2 s with PlayBatch.

Degradation: hold last Hub action ~0.45 s → local lip-safe jump/stay → halt after ~2 s (connection_lost). Contest and TypeSafe directors run on the Hub, not as keys in the place.

-- ServerScriptService.PlayBrain.server.lua
local ServerStorage = game:GetService("ServerStorage")
local PlayRuntime = require(game.ReplicatedStorage.AiarenaSdk.PlayRuntime)
local Policy = require(game.ReplicatedStorage.AiarenaSdk.Policy) -- encode only; no weights
local VisionSystem = require(game.ReplicatedStorage.AiarenaSdk.VisionSystem)
-- Copy VISION from roblox/train-my-ai/src/shared/TrainConfig.lua or inline:
local VISION = {
	range = 30,
	origin_offset_y = 1.5,
	forward_yaw = 180,
	yaw_angles = { -50, -30, -10, 10, 30, 50 },
	pitch_angles = { -20, 0, 20 },
	edge_distances = { 2, 4, 6 },
	ground_yaw = { -30, 0, 30 },
}

local license = ServerStorage:WaitForChild("ArenaGameLicense") -- StringValue, aig_…
local runtime = PlayRuntime.new({
	hubUrl = "https://aia-hub.metamindinglab.com",
	licenseKey = license.Value,
	gameId = "your-published-place",
	brainId = "hub-brain-uuid",
	roomSlug = "obby",
})

local TICK = 0.1

local function spawnLoop(npc)
	local humanoid = npc:WaitForChild("Humanoid")
	local hrp = npc:WaitForChild("HumanoidRootPart")
	task.spawn(function()
		while humanoid.Health > 0 do
			local vision = VisionSystem.scan(npc, VISION)
			local extra = {
				grounded = humanoid.FloorMaterial ~= Enum.Material.Air,
				falling = hrp.AssemblyLinearVelocity.Y < -2,
			}
			local obs = Policy.encode({
				agents = {
					{
						y = hrp.Position.Y,
						z = hrp.Position.Z,
						on_ground = extra.grounded,
						checkpoint = npc:GetAttribute("Checkpoint") or 0,
					},
				},
				perception_rays = vision.rays,
			}, "obby", extra)
			-- apply never waits on HTTP; requestAct is task.spawn inside PlayRuntime
			runtime:apply(humanoid, hrp, obs, 16)
			task.wait(TICK)
		end
	end)
end

Actions: m is 9-way move (0 stay … 8), j jump, f fire. Response JSON is { m, j, f, seq } only.

Load several AI players into one round

One aig_ in ServerStorage. One PlayRuntime.new per roster brain_id. Clone SKU templates from ServerStorage (rbxassetid — never Hub FBX). Offset spawns, shared collision, ≤20 AIs via PlayBatch + POST /play/act/batch. Full roster JSON + Lua: Deploy & compete.

Game-type guidance

Obby (obstacle course)

Moving pads — extra information the game must publish

Rays see the pad where it is this tick. Jump hang is ~0.5 s, so a still "platform ahead" hit is not enough. The Paths gym publishes a timing cheatsheet the overlay reads; the 32-dim map MLP does not invent it.

SignalWhat the game setsWhy
JumpNowBool pulse ~0.45 sPad will be aligned after hang — jump this window
JumpKindon (land) / off (leave)Two different jumps
Pad CFrameLive world pose each HeartbeatAim at the pad, not a static spawn
onMoverRider vs pad AABBLip rays hit THIS pad, not the next — ignore while riding
Rider carrySlide HRP by pad delta; keep velocityAnchored CFrame movers do not physics-carry
-- Heartbeat on an anchored mover (see GymKit.makeMovingPad)
pad:SetAttribute("JumpNow", inWindow)       -- true only in the hang-aligned pulse
pad:SetAttribute("JumpKind", riderOn and "off" or "on")
pad:SetAttribute("TimeUntil", inWindow and (pulseUntil - t) or 9)

-- Carry: CFrame animation does not take the NPC with it
hrp.CFrame = hrp.CFrame + (nowPos - lastPos)
hrp.AssemblyLinearVelocity = Vector3.new(padVelX, hrp.AssemblyLinearVelocity.Y, hrp.AssemblyLinearVelocity.Z)

-- Teacher / overlay reads (BrainRunner extra for room_slug paths):
-- extra.padJumpNow, extra.padJumpKind, extra.padX, extra.padZ, extra.onMover
-- Wait until JumpNow, then jump. Do not halt on the pad (walks off against carry).

Live POST /play/act still sends a 32-float map vector with no JumpNow bit. For a published moving-pad course, either pack these extras into your room obs or apply wait/NOW locally as overlay (not map BC).

Other extras the 32-dim vector does not contain

Training two rooms in parallel

Aiming / combat

Maze

The room slug is a hint, not a requirement. This project trains obby and aiming in parallel (two chats, two brains). Do not replace obby from an aiming chat, and do not park the course from an obby chat.

Common pitfalls

PitfallCauseFix
NPC walks on lava without dyingCanCollide=trueCanCollide=false + Touched Health=0
NPC falls through worldBaseplate gone, no pads under gapsPlatforms cover all gaps
NPC stuck at spawnSpawn in hazard or wallMove spawn to a safe platform
NPC can't reach goalGaps too wide or heights too tallGaps < 12 (train 2–4), heights < 5
NPC tunnels through lavaLava < 0.5 studsMake lava 1 stud tall
Dies on platform edgeLava overlaps padLava top at Y=1, below pad surface
Can't classify objectsUnrecognized namesPlatform_*, Hazard_*, …

Developer checklist

Campus training (first-party)

Studio sync: TrainServer ↔ roblox/train-my-ai/src/server/TrainServer.server.lua. Set TrainConfig.CAMPUS_SECRET to Hub ARENA_CAMPUS_SECRET when that env is enabled. Obby pass bar: two jumps and CP 2 while alive — ignore CP printed after DIED!. Course source: roblox/gyms/src/LocomotionEasy.lua.