From Maelstrom-1
Use this page when you already adopted 0.9.0-maelstrom.1 and want to move to 0.9.0-maelstrom.2.
Package Version
Section titled âPackage Versionâ[dependencies]Riptide = "riptide/core@0.9.0-maelstrom.2"Riptide.Server and Riptide.Client
Section titled âRiptide.Server and Riptide.ClientâMaelstrom-2 exposes complete side-specific API tables. Entry scripts should import the side they launch, and module lifecycle hooks receive that side-specific API directly.
-- Before, inside a client controllerRiptide.Network.FireServer("RequestPurchase", itemId)Riptide.State:Subscribe("coins", callback)
-- After, inside a client controllerRiptide.Network.FireServer("RequestPurchase", itemId)Riptide.State:Subscribe("coins", callback)-- StarterPlayerScripts/main.client.lualocal Riptide = require(ReplicatedStorage.Packages.Riptide).Client
Riptide.Launch({ ModulesFolder = Players.LocalPlayer.PlayerScripts.Controllers,})The root package surface is kept for compatibility, but side imports plus injected side APIs give cleaner autocomplete and prevent server/client API overlap.
Plugin Start Readiness
Section titled âPlugin Start ReadinessâMaelstrom-1 had plugin Start as an async dispatch. Maelstrom-2 treats plugin Start as a readiness barrier.
Plugin Load + InitGame module LoadGame module InitPlugin Start readinessPlayerLifecycle replayGame module StartIf a plugin Start yields forever, it will now block readiness until Descriptor.StartTimeout marks it Errored.
MyPlugin.Descriptor = { Name = "MyPlugin", Version = "0.1.0", Side = "Server", StartTimeout = 5,}Long-running work should be spawned inside Start:
function MyPlugin.Hooks:Start(sandbox) -- Bounded readiness work here.
task.spawn(function() while true do task.wait(60) sandbox:Log("background tick") end end)endPlugin Dependency Validation
Section titled âPlugin Dependency ValidationâMissing dependencies are strict. A plugin with a missing dependency is blocked, and plugins depending on that blocked plugin are blocked too.
MyPlugin.Descriptor = { Name = "NeedsProfiles", Dependencies = { "ProfilePlugin" },}If ProfilePlugin is not loaded, NeedsProfiles will not run.
Plugin Sandbox Restrictions
Section titled âPlugin Sandbox RestrictionsâNormal plugin sandboxes no longer expose raw core event APIs like OnCoreEvent. Use the supported sandbox APIs:
sandbox:OnNetworkEventsandbox:FireClientsandbox:FireAllClientssandbox:FireServersandbox:Emitsandbox:Onsandbox:Oncesandbox:GetPluginAPI
Async.Parallel Results
Section titled âAsync.Parallel ResultsâAsync.Parallel now returns result objects.
local results = Riptide.Async.Parallel(tasks, 5)
if results[1].ok then print(results[1].value)elseif results[1].timedOut then warn("task timed out")else warn(results[1].error)endUpdate old code that expected raw return values.
Typed Network Proxies
Section titled âTyped Network ProxiesâUse typed proxies for event-field autocomplete:
local Network = Riptide.Network.TypedServer<NetworkEvents>()Network.PurchaseResult:FireClient(player, true, "ok")The dynamic methods remain available:
Riptide.Network.FireClient(player, "PurchaseResult", true, "ok")Typed State Proxies
Section titled âTyped State ProxiesâUse typed state proxies for key-level autocomplete:
local State = Riptide.State.TypedServer<StateSchema>()State.coins:SetForPlayer(player, 100)Dynamic string-key methods remain available.
QA Runner Behavior
Section titled âQA Runner BehaviorâStudio QA runners now fail the whole run when a test module fails to require. If a test disappears from the pass count after migration, check the load phase logs first.
Validation Checklist
Section titled âValidation Checklistâ- Import
.Server/.Clientin entry scripts and use the injected side API in modules. - Audit plugin
Starthooks for forever loops and move loops into spawned tasks. - Add
Descriptor.StartTimeoutfor plugins that do bounded async setup. - Fix missing plugin dependencies instead of relying on silent skips.
- Update
Async.Parallelresult handling. - Run Lune and Studio QA.