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.
Files changed (248) hide show
  1. package/README.md +122 -0
  2. package/commands/setup-game.md +108 -0
  3. package/commands/sync-check.md +53 -0
  4. package/core/roblox-core.md +93 -0
  5. package/dist/server.js +167 -0
  6. package/package.json +35 -0
  7. package/skills/roblox-analytics/SKILL.md +277 -0
  8. package/skills/roblox-analytics/references/event-batcher.luau +75 -0
  9. package/skills/roblox-animation-vfx/SKILL.md +1325 -0
  10. package/skills/roblox-architecture/SKILL.md +863 -0
  11. package/skills/roblox-architecture/references/combat-systems.md +1381 -0
  12. package/skills/roblox-code-review/SKILL.md +687 -0
  13. package/skills/roblox-data/SKILL.md +889 -0
  14. package/skills/roblox-data/references/inventory-systems.md +1729 -0
  15. package/skills/roblox-debug/SKILL.md +99 -0
  16. package/skills/roblox-gui/SKILL.md +1103 -0
  17. package/skills/roblox-gui-fusion/SKILL.md +150 -0
  18. package/skills/roblox-gui-fusion/references/inventory.luau +427 -0
  19. package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -0
  20. package/skills/roblox-gui-fusion/references/shop.luau +411 -0
  21. package/skills/roblox-luau-mastery/SKILL.md +1519 -0
  22. package/skills/roblox-monetization/SKILL.md +1084 -0
  23. package/skills/roblox-monetization/references/process-receipt.luau +131 -0
  24. package/skills/roblox-networking/SKILL.md +669 -0
  25. package/skills/roblox-networking/references/remote-validator.luau +193 -0
  26. package/skills/roblox-publish-checklist/SKILL.md +128 -0
  27. package/skills/roblox-runtime/SKILL.md +753 -0
  28. package/skills/roblox-sharp-edges/SKILL.md +295 -0
  29. package/skills/roblox-sync/SKILL.md +126 -0
  30. package/skills/roblox-testing/SKILL.md +943 -0
  31. package/skills/roblox-tooling/SKILL.md +150 -0
  32. package/vendor/LICENSES/ProfileStore-LICENSE +201 -0
  33. package/vendor/LICENSES/RbxUtil-LICENSE +7 -0
  34. package/vendor/LICENSES/promise-LICENSE +21 -0
  35. package/vendor/LICENSES/t-LICENSE +21 -0
  36. package/vendor/LICENSES/testez-LICENSE +201 -0
  37. package/vendor/README.md +84 -0
  38. package/vendor/fusion/Animation/ExternalTime.luau +84 -0
  39. package/vendor/fusion/Animation/Spring.luau +322 -0
  40. package/vendor/fusion/Animation/Stopwatch.luau +128 -0
  41. package/vendor/fusion/Animation/Tween.luau +187 -0
  42. package/vendor/fusion/Animation/getTweenDuration.luau +27 -0
  43. package/vendor/fusion/Animation/getTweenRatio.luau +47 -0
  44. package/vendor/fusion/Animation/lerpType.luau +164 -0
  45. package/vendor/fusion/Animation/packType.luau +100 -0
  46. package/vendor/fusion/Animation/springCoefficients.luau +80 -0
  47. package/vendor/fusion/Animation/unpackType.luau +103 -0
  48. package/vendor/fusion/Colour/Oklab.luau +70 -0
  49. package/vendor/fusion/Colour/sRGB.luau +55 -0
  50. package/vendor/fusion/External.luau +168 -0
  51. package/vendor/fusion/ExternalDebug.luau +70 -0
  52. package/vendor/fusion/Graph/Observer.luau +114 -0
  53. package/vendor/fusion/Graph/castToGraph.luau +29 -0
  54. package/vendor/fusion/Graph/change.luau +81 -0
  55. package/vendor/fusion/Graph/depend.luau +33 -0
  56. package/vendor/fusion/Graph/evaluate.luau +56 -0
  57. package/vendor/fusion/Instances/Attribute.luau +58 -0
  58. package/vendor/fusion/Instances/AttributeChange.luau +47 -0
  59. package/vendor/fusion/Instances/AttributeOut.luau +63 -0
  60. package/vendor/fusion/Instances/Child.luau +21 -0
  61. package/vendor/fusion/Instances/Children.luau +148 -0
  62. package/vendor/fusion/Instances/Hydrate.luau +33 -0
  63. package/vendor/fusion/Instances/New.luau +53 -0
  64. package/vendor/fusion/Instances/OnChange.luau +50 -0
  65. package/vendor/fusion/Instances/OnEvent.luau +54 -0
  66. package/vendor/fusion/Instances/Out.luau +69 -0
  67. package/vendor/fusion/Instances/applyInstanceProps.luau +149 -0
  68. package/vendor/fusion/Instances/defaultProps.luau +194 -0
  69. package/vendor/fusion/LICENSE +21 -0
  70. package/vendor/fusion/Logging/formatError.luau +49 -0
  71. package/vendor/fusion/Logging/messages.luau +52 -0
  72. package/vendor/fusion/Logging/parseError.luau +25 -0
  73. package/vendor/fusion/Memory/checkLifetime.luau +134 -0
  74. package/vendor/fusion/Memory/deriveScope.luau +24 -0
  75. package/vendor/fusion/Memory/deriveScopeImpl.luau +45 -0
  76. package/vendor/fusion/Memory/doCleanup.luau +79 -0
  77. package/vendor/fusion/Memory/innerScope.luau +34 -0
  78. package/vendor/fusion/Memory/legacyCleanup.luau +18 -0
  79. package/vendor/fusion/Memory/needsDestruction.luau +17 -0
  80. package/vendor/fusion/Memory/poisonScope.luau +34 -0
  81. package/vendor/fusion/Memory/scopePool.luau +55 -0
  82. package/vendor/fusion/Memory/scoped.luau +27 -0
  83. package/vendor/fusion/Memory/whichLivesLonger.luau +75 -0
  84. package/vendor/fusion/RobloxExternal.luau +98 -0
  85. package/vendor/fusion/State/Computed.luau +139 -0
  86. package/vendor/fusion/State/For/Disassembly.luau +211 -0
  87. package/vendor/fusion/State/For/ForTypes.luau +30 -0
  88. package/vendor/fusion/State/For/init.luau +110 -0
  89. package/vendor/fusion/State/ForKeys.luau +94 -0
  90. package/vendor/fusion/State/ForPairs.luau +97 -0
  91. package/vendor/fusion/State/ForValues.luau +94 -0
  92. package/vendor/fusion/State/Value.luau +88 -0
  93. package/vendor/fusion/State/castToState.luau +26 -0
  94. package/vendor/fusion/State/peek.luau +31 -0
  95. package/vendor/fusion/State/updateAll.luau +1 -0
  96. package/vendor/fusion/Types.luau +314 -0
  97. package/vendor/fusion/Utility/Contextual.luau +91 -0
  98. package/vendor/fusion/Utility/Safe.luau +23 -0
  99. package/vendor/fusion/Utility/isSimilar.luau +29 -0
  100. package/vendor/fusion/Utility/merge.luau +35 -0
  101. package/vendor/fusion/Utility/nameOf.luau +35 -0
  102. package/vendor/fusion/Utility/never.luau +14 -0
  103. package/vendor/fusion/Utility/nicknames.luau +11 -0
  104. package/vendor/fusion/Utility/xtypeof.luau +27 -0
  105. package/vendor/fusion/init.luau +82 -0
  106. package/vendor/profilestore/init.luau +2243 -0
  107. package/vendor/promise/init.luau +1982 -0
  108. package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -0
  109. package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -0
  110. package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -0
  111. package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -0
  112. package/vendor/rbxutil/buffer-util/Types.luau +60 -0
  113. package/vendor/rbxutil/buffer-util/index.d.ts +153 -0
  114. package/vendor/rbxutil/buffer-util/init.luau +41 -0
  115. package/vendor/rbxutil/buffer-util/package.json +16 -0
  116. package/vendor/rbxutil/buffer-util/wally.toml +9 -0
  117. package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -0
  118. package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -0
  119. package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -0
  120. package/vendor/rbxutil/comm/Client/init.luau +135 -0
  121. package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -0
  122. package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -0
  123. package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -0
  124. package/vendor/rbxutil/comm/Server/init.luau +140 -0
  125. package/vendor/rbxutil/comm/Types.luau +18 -0
  126. package/vendor/rbxutil/comm/Util.luau +27 -0
  127. package/vendor/rbxutil/comm/init.luau +35 -0
  128. package/vendor/rbxutil/comm/wally.toml +13 -0
  129. package/vendor/rbxutil/component/init.luau +759 -0
  130. package/vendor/rbxutil/component/init.test.luau +311 -0
  131. package/vendor/rbxutil/component/wally.toml +14 -0
  132. package/vendor/rbxutil/concur/init.luau +542 -0
  133. package/vendor/rbxutil/concur/init.test.luau +364 -0
  134. package/vendor/rbxutil/concur/wally.toml +8 -0
  135. package/vendor/rbxutil/enum-list/init.luau +101 -0
  136. package/vendor/rbxutil/enum-list/init.test.luau +91 -0
  137. package/vendor/rbxutil/enum-list/wally.toml +8 -0
  138. package/vendor/rbxutil/find/index.d.ts +20 -0
  139. package/vendor/rbxutil/find/init.luau +44 -0
  140. package/vendor/rbxutil/find/package.json +17 -0
  141. package/vendor/rbxutil/find/wally.toml +8 -0
  142. package/vendor/rbxutil/input/Gamepad.luau +559 -0
  143. package/vendor/rbxutil/input/Keyboard.luau +124 -0
  144. package/vendor/rbxutil/input/Mouse.luau +278 -0
  145. package/vendor/rbxutil/input/PreferredInput.luau +91 -0
  146. package/vendor/rbxutil/input/Touch.luau +120 -0
  147. package/vendor/rbxutil/input/init.luau +33 -0
  148. package/vendor/rbxutil/input/wally.toml +12 -0
  149. package/vendor/rbxutil/loader/index.d.ts +15 -0
  150. package/vendor/rbxutil/loader/init.luau +137 -0
  151. package/vendor/rbxutil/loader/wally.toml +8 -0
  152. package/vendor/rbxutil/log/index.d.ts +38 -0
  153. package/vendor/rbxutil/log/init.luau +746 -0
  154. package/vendor/rbxutil/log/wally.toml +8 -0
  155. package/vendor/rbxutil/net/init.luau +190 -0
  156. package/vendor/rbxutil/net/wally.toml +8 -0
  157. package/vendor/rbxutil/option/index.d.ts +44 -0
  158. package/vendor/rbxutil/option/init.luau +489 -0
  159. package/vendor/rbxutil/option/init.test.luau +342 -0
  160. package/vendor/rbxutil/option/wally.toml +8 -0
  161. package/vendor/rbxutil/pid/index.d.ts +53 -0
  162. package/vendor/rbxutil/pid/init.luau +195 -0
  163. package/vendor/rbxutil/pid/package.json +16 -0
  164. package/vendor/rbxutil/pid/wally.toml +9 -0
  165. package/vendor/rbxutil/quaternion/index.d.ts +117 -0
  166. package/vendor/rbxutil/quaternion/init.luau +570 -0
  167. package/vendor/rbxutil/quaternion/package.json +16 -0
  168. package/vendor/rbxutil/quaternion/wally.toml +9 -0
  169. package/vendor/rbxutil/query/index.d.ts +43 -0
  170. package/vendor/rbxutil/query/init.luau +117 -0
  171. package/vendor/rbxutil/query/package.json +18 -0
  172. package/vendor/rbxutil/query/wally.toml +9 -0
  173. package/vendor/rbxutil/sequent/index.d.ts +28 -0
  174. package/vendor/rbxutil/sequent/init.luau +340 -0
  175. package/vendor/rbxutil/sequent/package.json +16 -0
  176. package/vendor/rbxutil/sequent/wally.toml +9 -0
  177. package/vendor/rbxutil/ser/init.luau +175 -0
  178. package/vendor/rbxutil/ser/init.test.luau +50 -0
  179. package/vendor/rbxutil/ser/wally.toml +11 -0
  180. package/vendor/rbxutil/shake/index.d.ts +36 -0
  181. package/vendor/rbxutil/shake/init.luau +532 -0
  182. package/vendor/rbxutil/shake/init.test.luau +267 -0
  183. package/vendor/rbxutil/shake/package.json +16 -0
  184. package/vendor/rbxutil/shake/wally.toml +9 -0
  185. package/vendor/rbxutil/signal/index.d.ts +100 -0
  186. package/vendor/rbxutil/signal/init.luau +432 -0
  187. package/vendor/rbxutil/signal/init.test.luau +190 -0
  188. package/vendor/rbxutil/signal/package.json +17 -0
  189. package/vendor/rbxutil/signal/wally.toml +9 -0
  190. package/vendor/rbxutil/silo/TableWatcher.luau +65 -0
  191. package/vendor/rbxutil/silo/Util.luau +55 -0
  192. package/vendor/rbxutil/silo/init.luau +338 -0
  193. package/vendor/rbxutil/silo/init.test.luau +215 -0
  194. package/vendor/rbxutil/silo/wally.toml +8 -0
  195. package/vendor/rbxutil/spring/index.d.ts +40 -0
  196. package/vendor/rbxutil/spring/init.luau +97 -0
  197. package/vendor/rbxutil/spring/package.json +17 -0
  198. package/vendor/rbxutil/spring/wally.toml +8 -0
  199. package/vendor/rbxutil/stream/index.d.ts +88 -0
  200. package/vendor/rbxutil/stream/init.luau +597 -0
  201. package/vendor/rbxutil/stream/package.json +18 -0
  202. package/vendor/rbxutil/stream/wally.toml +9 -0
  203. package/vendor/rbxutil/streamable/Streamable.luau +202 -0
  204. package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -0
  205. package/vendor/rbxutil/streamable/init.luau +8 -0
  206. package/vendor/rbxutil/streamable/wally.toml +12 -0
  207. package/vendor/rbxutil/symbol/init.luau +56 -0
  208. package/vendor/rbxutil/symbol/init.test.luau +37 -0
  209. package/vendor/rbxutil/symbol/wally.toml +8 -0
  210. package/vendor/rbxutil/table-util/init.luau +938 -0
  211. package/vendor/rbxutil/table-util/init.test.luau +439 -0
  212. package/vendor/rbxutil/table-util/wally.toml +8 -0
  213. package/vendor/rbxutil/task-queue/index.d.ts +27 -0
  214. package/vendor/rbxutil/task-queue/init.luau +97 -0
  215. package/vendor/rbxutil/task-queue/wally.toml +8 -0
  216. package/vendor/rbxutil/timer/index.d.ts +81 -0
  217. package/vendor/rbxutil/timer/init.luau +249 -0
  218. package/vendor/rbxutil/timer/init.test.luau +73 -0
  219. package/vendor/rbxutil/timer/wally.toml +11 -0
  220. package/vendor/rbxutil/tree/index.d.ts +15 -0
  221. package/vendor/rbxutil/tree/init.luau +137 -0
  222. package/vendor/rbxutil/tree/wally.toml +8 -0
  223. package/vendor/rbxutil/trove/index.d.ts +46 -0
  224. package/vendor/rbxutil/trove/init.luau +787 -0
  225. package/vendor/rbxutil/trove/init.test.luau +203 -0
  226. package/vendor/rbxutil/trove/wally.toml +8 -0
  227. package/vendor/rbxutil/typed-remote/init.luau +196 -0
  228. package/vendor/rbxutil/typed-remote/wally.toml +8 -0
  229. package/vendor/rbxutil/wait-for/index.d.ts +17 -0
  230. package/vendor/rbxutil/wait-for/init.luau +257 -0
  231. package/vendor/rbxutil/wait-for/init.test.luau +182 -0
  232. package/vendor/rbxutil/wait-for/wally.toml +11 -0
  233. package/vendor/t/t.lua +1350 -0
  234. package/vendor/testez/Context.lua +26 -0
  235. package/vendor/testez/Expectation.lua +311 -0
  236. package/vendor/testez/ExpectationContext.lua +38 -0
  237. package/vendor/testez/LifecycleHooks.lua +89 -0
  238. package/vendor/testez/Reporters/TeamCityReporter.lua +102 -0
  239. package/vendor/testez/Reporters/TextReporter.lua +106 -0
  240. package/vendor/testez/Reporters/TextReporterQuiet.lua +97 -0
  241. package/vendor/testez/TestBootstrap.lua +147 -0
  242. package/vendor/testez/TestEnum.lua +28 -0
  243. package/vendor/testez/TestPlan.lua +304 -0
  244. package/vendor/testez/TestPlanner.lua +40 -0
  245. package/vendor/testez/TestResults.lua +112 -0
  246. package/vendor/testez/TestRunner.lua +188 -0
  247. package/vendor/testez/TestSession.lua +243 -0
  248. 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.