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:
| Function | Description |
|---|---|
setclipboard / toclipboard | Copy text to clipboard |
writefile / readfile | File system access |
syn.request / http.request | HTTP 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.