Skip to main content

Roblox-Specific Gotchas

These aren't Pulse bugs — they're Roblox constraints. Understanding them saves hours of debugging.


Highlights get destroyed by replication

The problem: When you parent a Highlight instance to another player's character model, Roblox's replication system detects that the object doesn't belong there and silently destroys it. The highlight appears briefly then disappears.

This affects:

  • Other players' characters (game.Players.OtherPlayer.Character)
  • NPC models replicated from the server (workspace.Shifters.*, etc.)
  • Anything your client doesn't own

The fix: Parent all highlights to CoreGui (or PlayerGui as a fallback) and use the Adornee property:

local h = Instance.new("Highlight")
h.FillColor = Color3.fromRGB(255, 50, 50)
h.Adornee = targetModel -- points at the target
h.Parent = game:GetService("CoreGui") -- lives in your client's UI tree

Highlights on workspace.Titans.Alive children work because the local client owns those instances through the terrain/world replication. But anything else — other players, server-replicated models — requires the CoreGui approach.


workspace.CurrentCamera is nil at inject time

The problem: When you inject a script, the game may still be initialising. workspace.CurrentCamera is nil until a Camera object is created by the engine.

// At component setup — nil if game not loaded!
const cam = workspace.CurrentCamera
const sz = cam!.ViewportSize // error: attempt to index nil

The fix: Always wrap camera/world access in a load guard:

task.spawn(() => {
if (!game.IsLoaded()) game.Loaded.Wait()
const cam = workspace.CurrentCamera
// safe from here
})

Or read it lazily inside your handler instead of caching it at setup time:

on.renderStepped({ when: enabled }, () => {
const cam = workspace.CurrentCamera
if (!cam) return // still not ready, skip this frame
const sz = cam.ViewportSize
})

game:IsLoaded() vs waiting for specific instances

game.Loaded:Wait() waits for the basic game data model to be ready. But custom instances (your game's folders, workspace.Enemies, etc.) may still stream in after that.

For custom instances, use WaitForChild with a timeout:

local folder = workspace:WaitForChild("Enemies", 10)
if not folder then
Pulse.Log.warn("MyComp", "Enemies folder not found after 10s")
return
end

Don't use WaitForChild without a timeout — it will hang forever if the instance doesn't exist.


task.wait() minimum is ~0.03s, not zero

Calling task.wait(0) or task.wait(0.001) doesn't actually give back control for 1ms. The scheduler has a minimum heartbeat interval (roughly one frame at 60fps, about 0.016s). Relying on sub-frame wait precision will give inconsistent results.

If you need something to happen on the next frame, use task.defer:

task.defer(function()
-- runs at the end of the current heartbeat step
end)

RemoteEvent paths are case-sensitive

remote Attack = "remotes/combat/attack" -- WRONG if the actual path uses capitals
remote Attack = "Remotes/Combat/Attack" -- correct

Paths use WaitForChild which is case-sensitive. Double-check every folder name.


BillboardGui must be parented to a BasePart or UI container

BillboardGui with an Adornee works correctly when parented to PlayerGui or CoreGui. Parenting it directly to a Model (not a BasePart) gives inconsistent positioning because a Model has no single origin point.

local bg = Instance.new("BillboardGui")
bg.Adornee = model:FindFirstChild("HumanoidRootPart") -- BasePart
bg.Parent = game:GetService("CoreGui")

Executor differences in feature availability

Not all executors expose the same global functions. Some common ones that vary:

FunctionDescription
setclipboard / toclipboardCopy text to clipboard
writefile / readfileFile system access
syn.request / http.requestHTTP requests
getgenv()Global environment access

Pulse's dev overlay tries setclipboard, toclipboard, and writeclipboard in order. For your own code, always pcall executor-specific functions:

local ok = false
pcall(function() setclipboard("hello"); ok = true end)
if not ok then
Pulse.Log.warn("MyComp", "clipboard not available on this executor")
end

Humanoid.Health changes are not immediate

Setting humanoid.Health = humanoid.MaxHealth is replicated through the server in some games. The actual displayed health may not change instantly on the client. This is a game/server design issue, not Pulse's concern.


LocalPlayer.Character can be nil

At inject time and briefly after respawn, LocalPlayer.Character is nil. Any code that accesses it without a nil check will error. Pulse's humanoid, hrp, and character locals handle this correctly inside on handlers — but if you access _LocalPlayer.Character directly in your own code, always check first.