roblox-opencode 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/commands/setup-game.md +108 -0
- package/commands/sync-check.md +53 -0
- package/core/roblox-core.md +93 -0
- package/dist/server.js +167 -0
- package/package.json +35 -0
- package/skills/roblox-analytics/SKILL.md +277 -0
- package/skills/roblox-analytics/references/event-batcher.luau +75 -0
- package/skills/roblox-animation-vfx/SKILL.md +1325 -0
- package/skills/roblox-architecture/SKILL.md +863 -0
- package/skills/roblox-architecture/references/combat-systems.md +1381 -0
- package/skills/roblox-code-review/SKILL.md +687 -0
- package/skills/roblox-data/SKILL.md +889 -0
- package/skills/roblox-data/references/inventory-systems.md +1729 -0
- package/skills/roblox-debug/SKILL.md +99 -0
- package/skills/roblox-gui/SKILL.md +1103 -0
- package/skills/roblox-gui-fusion/SKILL.md +150 -0
- package/skills/roblox-gui-fusion/references/inventory.luau +427 -0
- package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -0
- package/skills/roblox-gui-fusion/references/shop.luau +411 -0
- package/skills/roblox-luau-mastery/SKILL.md +1519 -0
- package/skills/roblox-monetization/SKILL.md +1084 -0
- package/skills/roblox-monetization/references/process-receipt.luau +131 -0
- package/skills/roblox-networking/SKILL.md +669 -0
- package/skills/roblox-networking/references/remote-validator.luau +193 -0
- package/skills/roblox-publish-checklist/SKILL.md +128 -0
- package/skills/roblox-runtime/SKILL.md +753 -0
- package/skills/roblox-sharp-edges/SKILL.md +295 -0
- package/skills/roblox-sync/SKILL.md +126 -0
- package/skills/roblox-testing/SKILL.md +943 -0
- package/skills/roblox-tooling/SKILL.md +150 -0
- package/vendor/LICENSES/ProfileStore-LICENSE +201 -0
- package/vendor/LICENSES/RbxUtil-LICENSE +7 -0
- package/vendor/LICENSES/promise-LICENSE +21 -0
- package/vendor/LICENSES/t-LICENSE +21 -0
- package/vendor/LICENSES/testez-LICENSE +201 -0
- package/vendor/README.md +84 -0
- package/vendor/fusion/Animation/ExternalTime.luau +84 -0
- package/vendor/fusion/Animation/Spring.luau +322 -0
- package/vendor/fusion/Animation/Stopwatch.luau +128 -0
- package/vendor/fusion/Animation/Tween.luau +187 -0
- package/vendor/fusion/Animation/getTweenDuration.luau +27 -0
- package/vendor/fusion/Animation/getTweenRatio.luau +47 -0
- package/vendor/fusion/Animation/lerpType.luau +164 -0
- package/vendor/fusion/Animation/packType.luau +100 -0
- package/vendor/fusion/Animation/springCoefficients.luau +80 -0
- package/vendor/fusion/Animation/unpackType.luau +103 -0
- package/vendor/fusion/Colour/Oklab.luau +70 -0
- package/vendor/fusion/Colour/sRGB.luau +55 -0
- package/vendor/fusion/External.luau +168 -0
- package/vendor/fusion/ExternalDebug.luau +70 -0
- package/vendor/fusion/Graph/Observer.luau +114 -0
- package/vendor/fusion/Graph/castToGraph.luau +29 -0
- package/vendor/fusion/Graph/change.luau +81 -0
- package/vendor/fusion/Graph/depend.luau +33 -0
- package/vendor/fusion/Graph/evaluate.luau +56 -0
- package/vendor/fusion/Instances/Attribute.luau +58 -0
- package/vendor/fusion/Instances/AttributeChange.luau +47 -0
- package/vendor/fusion/Instances/AttributeOut.luau +63 -0
- package/vendor/fusion/Instances/Child.luau +21 -0
- package/vendor/fusion/Instances/Children.luau +148 -0
- package/vendor/fusion/Instances/Hydrate.luau +33 -0
- package/vendor/fusion/Instances/New.luau +53 -0
- package/vendor/fusion/Instances/OnChange.luau +50 -0
- package/vendor/fusion/Instances/OnEvent.luau +54 -0
- package/vendor/fusion/Instances/Out.luau +69 -0
- package/vendor/fusion/Instances/applyInstanceProps.luau +149 -0
- package/vendor/fusion/Instances/defaultProps.luau +194 -0
- package/vendor/fusion/LICENSE +21 -0
- package/vendor/fusion/Logging/formatError.luau +49 -0
- package/vendor/fusion/Logging/messages.luau +52 -0
- package/vendor/fusion/Logging/parseError.luau +25 -0
- package/vendor/fusion/Memory/checkLifetime.luau +134 -0
- package/vendor/fusion/Memory/deriveScope.luau +24 -0
- package/vendor/fusion/Memory/deriveScopeImpl.luau +45 -0
- package/vendor/fusion/Memory/doCleanup.luau +79 -0
- package/vendor/fusion/Memory/innerScope.luau +34 -0
- package/vendor/fusion/Memory/legacyCleanup.luau +18 -0
- package/vendor/fusion/Memory/needsDestruction.luau +17 -0
- package/vendor/fusion/Memory/poisonScope.luau +34 -0
- package/vendor/fusion/Memory/scopePool.luau +55 -0
- package/vendor/fusion/Memory/scoped.luau +27 -0
- package/vendor/fusion/Memory/whichLivesLonger.luau +75 -0
- package/vendor/fusion/RobloxExternal.luau +98 -0
- package/vendor/fusion/State/Computed.luau +139 -0
- package/vendor/fusion/State/For/Disassembly.luau +211 -0
- package/vendor/fusion/State/For/ForTypes.luau +30 -0
- package/vendor/fusion/State/For/init.luau +110 -0
- package/vendor/fusion/State/ForKeys.luau +94 -0
- package/vendor/fusion/State/ForPairs.luau +97 -0
- package/vendor/fusion/State/ForValues.luau +94 -0
- package/vendor/fusion/State/Value.luau +88 -0
- package/vendor/fusion/State/castToState.luau +26 -0
- package/vendor/fusion/State/peek.luau +31 -0
- package/vendor/fusion/State/updateAll.luau +1 -0
- package/vendor/fusion/Types.luau +314 -0
- package/vendor/fusion/Utility/Contextual.luau +91 -0
- package/vendor/fusion/Utility/Safe.luau +23 -0
- package/vendor/fusion/Utility/isSimilar.luau +29 -0
- package/vendor/fusion/Utility/merge.luau +35 -0
- package/vendor/fusion/Utility/nameOf.luau +35 -0
- package/vendor/fusion/Utility/never.luau +14 -0
- package/vendor/fusion/Utility/nicknames.luau +11 -0
- package/vendor/fusion/Utility/xtypeof.luau +27 -0
- package/vendor/fusion/init.luau +82 -0
- package/vendor/profilestore/init.luau +2243 -0
- package/vendor/promise/init.luau +1982 -0
- package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -0
- package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -0
- package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -0
- package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -0
- package/vendor/rbxutil/buffer-util/Types.luau +60 -0
- package/vendor/rbxutil/buffer-util/index.d.ts +153 -0
- package/vendor/rbxutil/buffer-util/init.luau +41 -0
- package/vendor/rbxutil/buffer-util/package.json +16 -0
- package/vendor/rbxutil/buffer-util/wally.toml +9 -0
- package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -0
- package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -0
- package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -0
- package/vendor/rbxutil/comm/Client/init.luau +135 -0
- package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -0
- package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -0
- package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -0
- package/vendor/rbxutil/comm/Server/init.luau +140 -0
- package/vendor/rbxutil/comm/Types.luau +18 -0
- package/vendor/rbxutil/comm/Util.luau +27 -0
- package/vendor/rbxutil/comm/init.luau +35 -0
- package/vendor/rbxutil/comm/wally.toml +13 -0
- package/vendor/rbxutil/component/init.luau +759 -0
- package/vendor/rbxutil/component/init.test.luau +311 -0
- package/vendor/rbxutil/component/wally.toml +14 -0
- package/vendor/rbxutil/concur/init.luau +542 -0
- package/vendor/rbxutil/concur/init.test.luau +364 -0
- package/vendor/rbxutil/concur/wally.toml +8 -0
- package/vendor/rbxutil/enum-list/init.luau +101 -0
- package/vendor/rbxutil/enum-list/init.test.luau +91 -0
- package/vendor/rbxutil/enum-list/wally.toml +8 -0
- package/vendor/rbxutil/find/index.d.ts +20 -0
- package/vendor/rbxutil/find/init.luau +44 -0
- package/vendor/rbxutil/find/package.json +17 -0
- package/vendor/rbxutil/find/wally.toml +8 -0
- package/vendor/rbxutil/input/Gamepad.luau +559 -0
- package/vendor/rbxutil/input/Keyboard.luau +124 -0
- package/vendor/rbxutil/input/Mouse.luau +278 -0
- package/vendor/rbxutil/input/PreferredInput.luau +91 -0
- package/vendor/rbxutil/input/Touch.luau +120 -0
- package/vendor/rbxutil/input/init.luau +33 -0
- package/vendor/rbxutil/input/wally.toml +12 -0
- package/vendor/rbxutil/loader/index.d.ts +15 -0
- package/vendor/rbxutil/loader/init.luau +137 -0
- package/vendor/rbxutil/loader/wally.toml +8 -0
- package/vendor/rbxutil/log/index.d.ts +38 -0
- package/vendor/rbxutil/log/init.luau +746 -0
- package/vendor/rbxutil/log/wally.toml +8 -0
- package/vendor/rbxutil/net/init.luau +190 -0
- package/vendor/rbxutil/net/wally.toml +8 -0
- package/vendor/rbxutil/option/index.d.ts +44 -0
- package/vendor/rbxutil/option/init.luau +489 -0
- package/vendor/rbxutil/option/init.test.luau +342 -0
- package/vendor/rbxutil/option/wally.toml +8 -0
- package/vendor/rbxutil/pid/index.d.ts +53 -0
- package/vendor/rbxutil/pid/init.luau +195 -0
- package/vendor/rbxutil/pid/package.json +16 -0
- package/vendor/rbxutil/pid/wally.toml +9 -0
- package/vendor/rbxutil/quaternion/index.d.ts +117 -0
- package/vendor/rbxutil/quaternion/init.luau +570 -0
- package/vendor/rbxutil/quaternion/package.json +16 -0
- package/vendor/rbxutil/quaternion/wally.toml +9 -0
- package/vendor/rbxutil/query/index.d.ts +43 -0
- package/vendor/rbxutil/query/init.luau +117 -0
- package/vendor/rbxutil/query/package.json +18 -0
- package/vendor/rbxutil/query/wally.toml +9 -0
- package/vendor/rbxutil/sequent/index.d.ts +28 -0
- package/vendor/rbxutil/sequent/init.luau +340 -0
- package/vendor/rbxutil/sequent/package.json +16 -0
- package/vendor/rbxutil/sequent/wally.toml +9 -0
- package/vendor/rbxutil/ser/init.luau +175 -0
- package/vendor/rbxutil/ser/init.test.luau +50 -0
- package/vendor/rbxutil/ser/wally.toml +11 -0
- package/vendor/rbxutil/shake/index.d.ts +36 -0
- package/vendor/rbxutil/shake/init.luau +532 -0
- package/vendor/rbxutil/shake/init.test.luau +267 -0
- package/vendor/rbxutil/shake/package.json +16 -0
- package/vendor/rbxutil/shake/wally.toml +9 -0
- package/vendor/rbxutil/signal/index.d.ts +100 -0
- package/vendor/rbxutil/signal/init.luau +432 -0
- package/vendor/rbxutil/signal/init.test.luau +190 -0
- package/vendor/rbxutil/signal/package.json +17 -0
- package/vendor/rbxutil/signal/wally.toml +9 -0
- package/vendor/rbxutil/silo/TableWatcher.luau +65 -0
- package/vendor/rbxutil/silo/Util.luau +55 -0
- package/vendor/rbxutil/silo/init.luau +338 -0
- package/vendor/rbxutil/silo/init.test.luau +215 -0
- package/vendor/rbxutil/silo/wally.toml +8 -0
- package/vendor/rbxutil/spring/index.d.ts +40 -0
- package/vendor/rbxutil/spring/init.luau +97 -0
- package/vendor/rbxutil/spring/package.json +17 -0
- package/vendor/rbxutil/spring/wally.toml +8 -0
- package/vendor/rbxutil/stream/index.d.ts +88 -0
- package/vendor/rbxutil/stream/init.luau +597 -0
- package/vendor/rbxutil/stream/package.json +18 -0
- package/vendor/rbxutil/stream/wally.toml +9 -0
- package/vendor/rbxutil/streamable/Streamable.luau +202 -0
- package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -0
- package/vendor/rbxutil/streamable/init.luau +8 -0
- package/vendor/rbxutil/streamable/wally.toml +12 -0
- package/vendor/rbxutil/symbol/init.luau +56 -0
- package/vendor/rbxutil/symbol/init.test.luau +37 -0
- package/vendor/rbxutil/symbol/wally.toml +8 -0
- package/vendor/rbxutil/table-util/init.luau +938 -0
- package/vendor/rbxutil/table-util/init.test.luau +439 -0
- package/vendor/rbxutil/table-util/wally.toml +8 -0
- package/vendor/rbxutil/task-queue/index.d.ts +27 -0
- package/vendor/rbxutil/task-queue/init.luau +97 -0
- package/vendor/rbxutil/task-queue/wally.toml +8 -0
- package/vendor/rbxutil/timer/index.d.ts +81 -0
- package/vendor/rbxutil/timer/init.luau +249 -0
- package/vendor/rbxutil/timer/init.test.luau +73 -0
- package/vendor/rbxutil/timer/wally.toml +11 -0
- package/vendor/rbxutil/tree/index.d.ts +15 -0
- package/vendor/rbxutil/tree/init.luau +137 -0
- package/vendor/rbxutil/tree/wally.toml +8 -0
- package/vendor/rbxutil/trove/index.d.ts +46 -0
- package/vendor/rbxutil/trove/init.luau +787 -0
- package/vendor/rbxutil/trove/init.test.luau +203 -0
- package/vendor/rbxutil/trove/wally.toml +8 -0
- package/vendor/rbxutil/typed-remote/init.luau +196 -0
- package/vendor/rbxutil/typed-remote/wally.toml +8 -0
- package/vendor/rbxutil/wait-for/index.d.ts +17 -0
- package/vendor/rbxutil/wait-for/init.luau +257 -0
- package/vendor/rbxutil/wait-for/init.test.luau +182 -0
- package/vendor/rbxutil/wait-for/wally.toml +11 -0
- package/vendor/t/t.lua +1350 -0
- package/vendor/testez/Context.lua +26 -0
- package/vendor/testez/Expectation.lua +311 -0
- package/vendor/testez/ExpectationContext.lua +38 -0
- package/vendor/testez/LifecycleHooks.lua +89 -0
- package/vendor/testez/Reporters/TeamCityReporter.lua +102 -0
- package/vendor/testez/Reporters/TextReporter.lua +106 -0
- package/vendor/testez/Reporters/TextReporterQuiet.lua +97 -0
- package/vendor/testez/TestBootstrap.lua +147 -0
- package/vendor/testez/TestEnum.lua +28 -0
- package/vendor/testez/TestPlan.lua +304 -0
- package/vendor/testez/TestPlanner.lua +40 -0
- package/vendor/testez/TestResults.lua +112 -0
- package/vendor/testez/TestRunner.lua +188 -0
- package/vendor/testez/TestSession.lua +243 -0
- package/vendor/testez/init.lua +40 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roblox-sharp-edges
|
|
3
|
+
description: >
|
|
4
|
+
12 production footguns ranked by severity. Data loss, exploits, memory leaks, mobile perf.
|
|
5
|
+
last_reviewed: 2026-05-22
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<!-- Source: brockmartin/roblox-game-skill (MIT) -->
|
|
9
|
+
|
|
10
|
+
# Roblox Sharp Edges (Gotchas) Reference
|
|
11
|
+
|
|
12
|
+
> Every entry here represents a real production footgun that has caused data loss, exploits,
|
|
13
|
+
> crashes, or hours of debugging in Roblox games.
|
|
14
|
+
>
|
|
15
|
+
> **Severity Levels:**
|
|
16
|
+
> - **Critical** - Data loss, security breach, or revenue loss. Fix before shipping.
|
|
17
|
+
> - **High** - Server instability, degraded experience, or exploit surface. Fix in current sprint.
|
|
18
|
+
> - **Medium** - Correctness bugs, performance issues, or dev confusion. Fix before scale.
|
|
19
|
+
> - **Low** - Code quality, maintainability, or minor timing issues. Fix when convenient.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## SE-1 | Critical | DataStore Data Loss from Session Handling
|
|
24
|
+
|
|
25
|
+
**See roblox-data → Session Locking and ProfileStore for full details.**
|
|
26
|
+
|
|
27
|
+
When a player server-hops, the old server may still be saving while the new server loads stale data. ProfileStore handles session locking automatically - only one server owns a player's data at a time. Never use raw DataStoreService for player data without session locking.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## SE-2 | Critical | Client-Side Currency Manipulation
|
|
32
|
+
|
|
33
|
+
**See roblox-networking → Security Hardening for full details.**
|
|
34
|
+
|
|
35
|
+
Currency and all authoritative game state must live exclusively on the server. Never accept currency amounts from the client. The server computes all transactions internally and pushes display-only updates to the client. This is the single most common exploit in Roblox games.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## SE-3 | Critical | ProcessReceipt Mishandling
|
|
40
|
+
|
|
41
|
+
**See roblox-monetization → ProcessReceipt for full details.**
|
|
42
|
+
|
|
43
|
+
`MarketplaceService.ProcessReceipt` must return `PurchaseGranted` ONLY after the item is granted AND saved. If you return `PurchaseGranted` before granting, the player loses Robux. If you don't return it, Roblox retries on every join - potentially granting duplicates. Grant first, save second, return third.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## SE-4 | High | Memory Leaks from Undisconnected Events
|
|
48
|
+
|
|
49
|
+
### Problem
|
|
50
|
+
|
|
51
|
+
Every `:Connect()` returns an `RBXScriptConnection`. If you never `:Disconnect()` it, the connection persists for the script's lifetime - even after the object is destroyed. In per-player systems, memory grows linearly with every player who has ever joined.
|
|
52
|
+
|
|
53
|
+
### Symptoms
|
|
54
|
+
|
|
55
|
+
- Server memory climbs steadily over time.
|
|
56
|
+
- Server FPS degrades after hours.
|
|
57
|
+
- Callbacks fire for players who left.
|
|
58
|
+
|
|
59
|
+
### Solution
|
|
60
|
+
|
|
61
|
+
Use the vendored **Trove** module (`vendor/rbxutil/trove/`) to group connections per-player and clean them all on `PlayerRemoving`:
|
|
62
|
+
|
|
63
|
+
```luau
|
|
64
|
+
local Players = game:GetService("Players")
|
|
65
|
+
local Trove = require(game.ReplicatedStorage.Packages.Trove)
|
|
66
|
+
|
|
67
|
+
local playerTroves: { [Player]: typeof(Trove.new()) } = {}
|
|
68
|
+
|
|
69
|
+
local function onPlayerAdded(player: Player)
|
|
70
|
+
local trove = Trove.new()
|
|
71
|
+
playerTroves[player] = trove
|
|
72
|
+
|
|
73
|
+
trove:Connect(player.CharacterAdded, function(character)
|
|
74
|
+
local humanoid = character:WaitForChild("Humanoid")
|
|
75
|
+
trove:Connect(humanoid.Died, function()
|
|
76
|
+
task.wait(3)
|
|
77
|
+
player:LoadCharacter()
|
|
78
|
+
end)
|
|
79
|
+
end)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
local function onPlayerRemoving(player: Player)
|
|
83
|
+
local trove = playerTroves[player]
|
|
84
|
+
if trove then
|
|
85
|
+
trove:Clean()
|
|
86
|
+
playerTroves[player] = nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Players.PlayerAdded:Connect(onPlayerAdded)
|
|
91
|
+
Players.PlayerRemoving:Connect(onPlayerRemoving)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## SE-5 | High | RemoteEvent Flooding
|
|
97
|
+
|
|
98
|
+
### Problem
|
|
99
|
+
|
|
100
|
+
RemoteEvents have no built-in rate limiting. Exploiters can fire thousands of times per second, flooding the server with DataStore calls, instance creation, or raycasts.
|
|
101
|
+
|
|
102
|
+
### Solution
|
|
103
|
+
|
|
104
|
+
Implement per-player, per-remote rate limiting on the server. See **roblox-networking → Rate Limiting** for production patterns.
|
|
105
|
+
|
|
106
|
+
Minimal inline example:
|
|
107
|
+
|
|
108
|
+
```luau
|
|
109
|
+
local lastFire: { [Player]: number } = {}
|
|
110
|
+
local COOLDOWN = 0.1
|
|
111
|
+
|
|
112
|
+
AttackRemote.OnServerEvent:Connect(function(player: Player, targetId: number)
|
|
113
|
+
local now = os.clock()
|
|
114
|
+
if lastFire[player] and now - lastFire[player] < COOLDOWN then return end
|
|
115
|
+
lastFire[player] = now
|
|
116
|
+
-- process attack
|
|
117
|
+
end)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## SE-6 | High | BindToClose Timeout
|
|
123
|
+
|
|
124
|
+
**See roblox-data → Best Practices (BindToClose Handler) for full details.**
|
|
125
|
+
|
|
126
|
+
`game:BindToClose()` gives at most 30 seconds. If using ProfileStore, this is automatic. If using raw DataStore, save all players in parallel with `task.spawn` - sequential saves with 50 players will timeout.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## SE-7 | Medium | Part Count on Mobile
|
|
131
|
+
|
|
132
|
+
Mobile devices struggle above ~10,000 visible parts. Enable **StreamingEnabled** and configure `StreamingMinRadius`/`StreamingTargetRadius`. Use `ModelStreamingMode` to mark distant models as Opportunistic and gameplay-critical models as Persistent.
|
|
133
|
+
|
|
134
|
+
See **roblox-runtime → StreamingEnabled** for configuration details.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## SE-8 | Medium | Yielding in Module Require
|
|
139
|
+
|
|
140
|
+
### Problem
|
|
141
|
+
|
|
142
|
+
`require()` executes the module body synchronously. If it yields (`WaitForChild`, `task.wait`, HTTP), every script requiring that module blocks. Two modules requiring each other with yields = deadlock.
|
|
143
|
+
|
|
144
|
+
### Solution
|
|
145
|
+
|
|
146
|
+
Never yield in a module body. Use Init/Start lifecycle:
|
|
147
|
+
|
|
148
|
+
```luau
|
|
149
|
+
local CombatSystem = {}
|
|
150
|
+
|
|
151
|
+
function CombatSystem:Init()
|
|
152
|
+
-- WaitForChild is safe here (called by bootstrap, not during require)
|
|
153
|
+
self._remotes = game.ReplicatedStorage:WaitForChild("Remotes", 10)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
function CombatSystem:Start()
|
|
157
|
+
-- Connect events after all modules are Init'd
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
return CombatSystem
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Bootstrap script calls `:Init()` on all modules, then `:Start()` on all modules.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## SE-9 | Medium | Table Length with Nil Gaps
|
|
168
|
+
|
|
169
|
+
### Problem
|
|
170
|
+
|
|
171
|
+
`#` is only reliable for sequence tables (consecutive integer keys, no nil gaps). Setting `tbl[3] = nil` creates a hole; `#tbl` may return any valid boundary.
|
|
172
|
+
|
|
173
|
+
### Solution
|
|
174
|
+
|
|
175
|
+
- Never set array elements to `nil`. Use `table.remove()` to shift elements.
|
|
176
|
+
- Use generalized iteration (`for _, v in tbl do`) instead of `for i = 1, #tbl`.
|
|
177
|
+
- For sparse data, use dictionary keys instead of integer indices.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## SE-10 | Low | Deprecated wait()/spawn()/delay()
|
|
182
|
+
|
|
183
|
+
**See roblox-luau-mastery → Task Library for full details.**
|
|
184
|
+
|
|
185
|
+
Replace `wait()` → `task.wait()`, `spawn()` → `task.spawn()`, `delay()` → `task.delay()`. Legacy functions have minimum yield issues, unpredictable timing, and swallow errors.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## SE-11 | Medium | Infinite Yield Warning
|
|
190
|
+
|
|
191
|
+
### Problem
|
|
192
|
+
|
|
193
|
+
`WaitForChild(name)` without a timeout yields forever if the child never appears. Common with renamed instances, StreamingEnabled, or race conditions.
|
|
194
|
+
|
|
195
|
+
### Solution
|
|
196
|
+
|
|
197
|
+
Always pass a timeout. Handle `nil` return:
|
|
198
|
+
|
|
199
|
+
```luau
|
|
200
|
+
local folder = ReplicatedStorage:WaitForChild("Weapons", 10)
|
|
201
|
+
if not folder then
|
|
202
|
+
warn("[Init] Weapons folder not found after 10s")
|
|
203
|
+
return
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## SE-12 | Low | String Patterns vs Regex
|
|
210
|
+
|
|
211
|
+
### Problem
|
|
212
|
+
|
|
213
|
+
Luau uses Lua string patterns, not regex. `\d` doesn't work - use `%d`. Escape with `%` not `\`. No alternation (`|`), no non-greedy `*?` (use `-` instead), no lookahead.
|
|
214
|
+
|
|
215
|
+
### Key Differences
|
|
216
|
+
|
|
217
|
+
- Digits: `%d` not `\d`
|
|
218
|
+
- Word chars: `%w` not `\w`
|
|
219
|
+
- Whitespace: `%s` not `\s`
|
|
220
|
+
- Escape special chars: `%.` not `\.`
|
|
221
|
+
- Non-greedy: `.-` not `.*?`
|
|
222
|
+
- Literal `%`: `%%`
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## SE-13 | Medium | Local Function Declaration Order
|
|
227
|
+
|
|
228
|
+
### Problem
|
|
229
|
+
|
|
230
|
+
Luau has no hoisting. A `local function` is invisible to code above its declaration. AI assistants frequently place helper functions below the functions that call them, causing nil-value runtime errors.
|
|
231
|
+
|
|
232
|
+
### Rule
|
|
233
|
+
|
|
234
|
+
**Callees above callers. Always.** If `functionA()` calls `helperB()`, then `helperB` must be declared first.
|
|
235
|
+
|
|
236
|
+
```luau
|
|
237
|
+
-- BAD: helperB is nil when functionA runs
|
|
238
|
+
local function functionA()
|
|
239
|
+
helperB() -- ERROR: attempt to call a nil value
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
local function helperB()
|
|
243
|
+
print("I'm a helper")
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
-- GOOD: helper declared first
|
|
247
|
+
local function helperB()
|
|
248
|
+
print("I'm a helper")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
local function functionA()
|
|
252
|
+
helperB() -- works
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### When you need mutual recursion
|
|
257
|
+
|
|
258
|
+
Use forward declaration:
|
|
259
|
+
|
|
260
|
+
```luau
|
|
261
|
+
local functionB -- forward declare
|
|
262
|
+
local function functionA()
|
|
263
|
+
functionB()
|
|
264
|
+
end
|
|
265
|
+
function functionB() -- note: no 'local' (already declared above)
|
|
266
|
+
functionA()
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Quick Reference
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
CRITICAL (fix before shipping):
|
|
276
|
+
SE-1 DataStore session locking → Use ProfileStore
|
|
277
|
+
SE-2 Client-side currency → Server-authoritative only
|
|
278
|
+
SE-3 ProcessReceipt order → Grant THEN PurchaseGranted
|
|
279
|
+
|
|
280
|
+
HIGH (fix in current sprint):
|
|
281
|
+
SE-4 Undisconnected events → Trove pattern (vendored)
|
|
282
|
+
SE-5 RemoteEvent flooding → Per-player rate limiter
|
|
283
|
+
SE-6 BindToClose 30s timeout → Parallel saves with task.spawn
|
|
284
|
+
|
|
285
|
+
MEDIUM (fix before scale):
|
|
286
|
+
SE-7 Mobile part count → StreamingEnabled + <10K parts
|
|
287
|
+
SE-8 Yielding in module require → Init/Start lifecycle pattern
|
|
288
|
+
SE-9 Table # with nil gaps → table.remove or explicit length
|
|
289
|
+
SE-11 Infinite yield WaitForChild → Always pass timeout parameter
|
|
290
|
+
SE-13 Local function order → Callees above callers (no hoisting)
|
|
291
|
+
|
|
292
|
+
LOW (fix when convenient):
|
|
293
|
+
SE-10 Deprecated wait/spawn/delay → task.wait/spawn/delay
|
|
294
|
+
SE-12 String patterns vs regex → %d not \d, % not \
|
|
295
|
+
```
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roblox-sync
|
|
3
|
+
description: >
|
|
4
|
+
Studio Script Sync setup, troubleshooting, mode detection.
|
|
5
|
+
Canonical filesystem sync path for Roblox development.
|
|
6
|
+
last_reviewed: 2026-05-21
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<!-- Source: Script Sync walkthrough compiled from Roblox DevForum + creator-docs (MIT) -->
|
|
10
|
+
|
|
11
|
+
# Studio Script Sync
|
|
12
|
+
|
|
13
|
+
## Why This Is the Canonical Sync Path
|
|
14
|
+
|
|
15
|
+
| Tool | Setup steps | TeamCreate | Project file required |
|
|
16
|
+
|------|-------------|------------|------------------------|
|
|
17
|
+
| Studio Script Sync | 2 (toggle + right-click) | Yes | No |
|
|
18
|
+
| Rojo | 6+ (install aftman, install Rojo, project.json, install plugin, serve, connect) | No | Yes |
|
|
19
|
+
| Argon | 5 (install CLI, install plugin, init project, serve, connect) | No | Yes |
|
|
20
|
+
| Pesto | 4 (install CLI, install plugin, init, sync) | Partial | Yes |
|
|
21
|
+
|
|
22
|
+
## Setup Walkthrough
|
|
23
|
+
|
|
24
|
+
1. In Roblox Studio, open **File → Beta Features**. Scroll to "Script Sync." Toggle it on. Restart Studio.
|
|
25
|
+
2. In Explorer, right-click the top-level container holding your scripts (commonly ServerScriptService, ReplicatedStorage, StarterPlayer → StarterPlayerScripts). Click **Start Sync**.
|
|
26
|
+
3. Pick a folder on disk. Suggested layout: `~/projects/<game-name>/src/<container-name>/`. Studio will mirror the script tree into that folder as `.luau` files.
|
|
27
|
+
4. Repeat for each top-level container that holds scripts. Sync auto-resumes when Studio restarts.
|
|
28
|
+
|
|
29
|
+
## Capacity
|
|
30
|
+
|
|
31
|
+
- Up to 10000 scripts per top-level instance
|
|
32
|
+
- Up to 128 top-level instances synced at once
|
|
33
|
+
- Two-way real-time sync. Edits in either direction propagate.
|
|
34
|
+
|
|
35
|
+
## What Sync Does and Doesn't
|
|
36
|
+
|
|
37
|
+
**Does:**
|
|
38
|
+
- Bidirectional `.luau` file mirror for scripts (Script, LocalScript, ModuleScript)
|
|
39
|
+
- Auto-resume on Studio restart
|
|
40
|
+
- Works with TeamCreate
|
|
41
|
+
- Surfaces errors in UI when sync fails
|
|
42
|
+
|
|
43
|
+
**Doesn't:**
|
|
44
|
+
- Sync non-script instances (Parts, Models, Folders without scripts)
|
|
45
|
+
- Sync empty folders (use `.gitkeep`)
|
|
46
|
+
- Sync PackageLink instance metadata (package script contents do sync)
|
|
47
|
+
- Provide sourcemaps (you still need Rojo for that)
|
|
48
|
+
|
|
49
|
+
## Mode Detection
|
|
50
|
+
|
|
51
|
+
Every session, the agent detects the active mode:
|
|
52
|
+
|
|
53
|
+
- **Sync Mode** (filesystem `.luau` files exist): read/write/edit via filesystem. MCP only for verification, playtest, scene ops, asset insertion. Never read a script via MCP if its file exists on disk.
|
|
54
|
+
- **MCP-Only Mode** (no `.luau` files on disk): minimize reads, prefer `run_code` for inspection, batch edits behind ChangeHistoryService Recording. Recommend enabling sync.
|
|
55
|
+
|
|
56
|
+
If MCP-only and project exceeds light-touch scope (>500 lines/module or >10 modules), proactively recommend Sync Mode before continuing.
|
|
57
|
+
|
|
58
|
+
## Common Issues
|
|
59
|
+
|
|
60
|
+
- **Sync silently stops after Studio update**: restart Studio + re-Start Sync.
|
|
61
|
+
- **Empty folders deleted on git pull**: add `.gitkeep`.
|
|
62
|
+
- **Sync errors not auto-detected**: restart Studio. Roblox is fixing this.
|
|
63
|
+
- **Duplicate-name hierarchy errors**: avoid duplicate names within a sync container.
|
|
64
|
+
|
|
65
|
+
## Version Control with Git
|
|
66
|
+
|
|
67
|
+
Script Sync puts `.luau` files on disk. Git works with files on disk. Use them together.
|
|
68
|
+
|
|
69
|
+
### Setup (after sync is confirmed)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd ~/projects/<game-name>
|
|
73
|
+
git init
|
|
74
|
+
git add .
|
|
75
|
+
git commit -m "initial sync"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### .gitignore for Roblox projects
|
|
79
|
+
|
|
80
|
+
```gitignore
|
|
81
|
+
# OpenCode
|
|
82
|
+
.opencode/
|
|
83
|
+
|
|
84
|
+
# Roblox build files (binary, don't track)
|
|
85
|
+
*.rbxl
|
|
86
|
+
*.rbxlx
|
|
87
|
+
*.rbxm
|
|
88
|
+
*.rbxmx
|
|
89
|
+
|
|
90
|
+
# OS
|
|
91
|
+
.DS_Store
|
|
92
|
+
Thumbs.db
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Agent responsibilities
|
|
96
|
+
|
|
97
|
+
- **Suggest commits at natural breakpoints.** After completing a system, fixing a batch of bugs, or before a risky change: "Want to commit what we've built so far?"
|
|
98
|
+
- **Use git diff to review changes.** Before committing, `git diff` to show the user what changed. This is the review loop.
|
|
99
|
+
- **Commit messages should be descriptive.** "add DataService with ProfileStore session locking" not "update files."
|
|
100
|
+
- **Branch for risky work.** If the user wants to experiment (new architecture, major refactor), suggest a branch first: `git checkout -b refactor/combat-system`.
|
|
101
|
+
- **Don't auto-commit without asking.** Always confirm before `git commit`. The user should see what's being committed.
|
|
102
|
+
|
|
103
|
+
### Common git operations the agent should know
|
|
104
|
+
|
|
105
|
+
| Operation | Command | When |
|
|
106
|
+
|-----------|---------|------|
|
|
107
|
+
| See what changed | `git diff` | Before committing |
|
|
108
|
+
| Stage changes | `git add -A` or `git add <file>` | Before committing |
|
|
109
|
+
| Commit | `git commit -m "message"` | After staging |
|
|
110
|
+
| See history | `git log --oneline -10` | When user asks "what changed" |
|
|
111
|
+
| Undo last commit (keep changes) | `git reset --soft HEAD~1` | When user wants to redo commit |
|
|
112
|
+
| Create branch | `git checkout -b <name>` | Before risky/experimental work |
|
|
113
|
+
| Switch branch | `git checkout <name>` | When switching context |
|
|
114
|
+
| Merge branch | `git merge <name>` | When experimental work is ready |
|
|
115
|
+
|
|
116
|
+
### Conflict handling
|
|
117
|
+
|
|
118
|
+
If git merge conflicts occur in `.luau` files, the agent should:
|
|
119
|
+
1. Read the conflicting files
|
|
120
|
+
2. Show the user the conflict markers
|
|
121
|
+
3. Suggest a resolution based on what each side changed
|
|
122
|
+
4. Help resolve and commit
|
|
123
|
+
|
|
124
|
+
## If User Already Has Rojo/Argon
|
|
125
|
+
|
|
126
|
+
Detect presence of `default.project.json` (Rojo) or `*.project.json` with Argon-specific fields. If found: filesystem is already source of truth, operate normally on disk. Don't suggest switching to Script Sync.
|