Skip to content
Engine

Help

Engine Changelog

What changed in each version of Engine, newest first, and what to change in your game when a version asks for it.

How version numbers work is explained in Updates and Versions. With automatic updates, every new server runs a version as soon as it is published; with manual updates, your game moves to it when you update from the plugin. Changes to the plugin and the shell it installs are in the Plugin Changelog.

1.2.0

  • ChangedBreaking

    profile:Set(path, value) and profile:Update(path, transform) on DataService profiles no longer create missing tables on the path, and no longer replace a value on the path that is not a table. They error with the part of the path that is the problem ("Stats does not exist", "Coins is not a table") and change nothing. The last key may still be new: profile:Set("Stats.Kills", 0) works when Stats exists, so create a table first with profile:Set("Stats", {}). profile:Remove(path) on something that does not exist returns nil and changes nothing. Patch.Set(root, keys, value) follows the same rule. EconomySystem, InventorySystem and MonetizationService add the tables of their own record themselves, so they keep working on a store you bind. EconomySystem and InventorySystem report a value of your own where they keep their record (a string at Economy, for example) as an error instead of overwriting it. See Reading and writing data and Updating from 1.1.1.

  • ChangedBreaking

    MonetizationService no longer binds itself to the first profile store a player session starts in. It keeps its records in a profile only after MonetizationService:BindProfileStore(store), or when ProfileStoreName in its configuration names the store. Without either, records live in memory for the session and product receipts are deduplicated on that server only, with a warning. See Where purchases are stored.

  • Added

    DataService:CreateExtendedStore(name, scope?), with GetExtendedStore and FindExtendedStore: a store for entries that need more room than the 4 MB one DataStore key holds, such as the build on a plot. Every entry has one master key with Data, the small essential data you choose, and named sections for the big data. Each section is saved in keys of its own, split over several keys when it is bigger than one. A failed or interrupted save never leaves half-written data, and every section is checked when it is read. Every call costs more DataStore requests than a Store, profile store or Document, so use it only for entries that do not fit in one key. New settings: ExtendedStoreChunkSize, ExtendedStorePendingTimeout and ExtendedStoreParallelRequests. See Extended stores.

  • Added

    InventorySystem, on the server and the client: items, containers, equipment, crafting and world pickups, built around Roblox Tools. Every player gets the containers in PlayerContainers (by default one 30-slot Backpack), saved in a DataService profile and mirrored to their client. Items with a Tool are Tools in the player's Backpack, given back after every respawn. Equipment puts Accessories on the character and adds up stats. Recipes take ingredients, kept tools and currency costs, with unlocks and crafting stations. Chests and world pickups hold items outside a player, and items can be sold, or bought in item shops, through EconomySystem. The client sees its own containers and anything opened to it, and sends requests that the server checks. Inventories live in the system's own profile store, Engine.Inventory, unless you call InventorySystem:BindProfileStore(store).

  • Added

    ModerationSystem handles the violations Roblox reports in your experience. When it loads, it binds a violation processor with ModerationService:BindViolationProcessor. Every report fires ModerationSystem.RobloxViolation(violation, player?), is written to the audit log as RobloxViolation, and records a Violation sanction worth RobloxViolationPoints, so your escalation ladders act on Roblox's reports the same way they act on chat violations. Set ModerationSystem.OnRobloxViolation to pick the consequence yourself, and read ModerationSystem.IsViolationProcessorBound to see whether the processor is bound. New settings: RobloxViolations, RobloxViolationPriority, RobloxViolationKind, RobloxViolationPoints, AcknowledgeRobloxViolations and DefaultViolationReason. If your game binds its own violation processor, set the priority or AcknowledgeRobloxViolations = false so both see the report. See Roblox violations.

  • Added

    ModerationSystem:MockViolationAsync(report) sends a test report through ModerationService:MockViolationCallbackAsync, so you can try your violation handling in Studio.

  • Added

    GroupRoles on ModerationSystem:DefineRole and on the roles in the Roles setting: { [groupId] = { roleId or role name, ... } } gives the role to anyone holding one of those group roles. Groups can give a member several roles now, and a rank no longer says where a role sits, so this is the reliable way to map group staff. GroupRanks keeps working. See DefineRole.

  • Fixed

    ModerationSystem:FilterTextAsync(text, from, { For = player }) returned an empty string. It used TextFilterResult:GetChatForUserAsync, which Roblox deprecated and which now always answers with an empty string. It uses GetNonChatStringForUserAsync now.

  • Changed

    ModerationSystem no longer reads Players.BanningEnabled: scripts cannot read it, so the check never worked. With UseRobloxBans on, a ban always calls Players:BanAsync. When banning is off in Game Settings, that call fails and fires ModerationSystem.RobloxBanFailed as before, and the moderation record still enforces the ban.

  • Added

    MonetizationService:GetRobuxTransferEligibilityAsync(sender, receiverUserId), which resolves with "Eligible", "SenderNotEligible", "ReceiverNotEligible" or "Unknown", and MonetizationService:CanTransferRobuxAsync(sender, receiverUserId). Both use MarketplaceService:CanUsersTransferRobuxAsync, cached per sender and receiver for TransferEligibilityCacheSeconds. See Robux transfers.

  • Changed

    MonetizationService:PromptRobuxTransferAsync first asks Roblox whether both users may transfer Robux. When one of them may not, it rejects with "SenderNotEligible" or "ReceiverNotEligible" without opening a prompt, and fires the new MonetizationService.TransferRefused(sender, receiverUserId, reason). When Roblox cannot tell, the prompt opens as before. Turn the check off with CheckTransferEligibility = false.

  • Added

    PlayerService:GetGroupRolesAsync(target, groupId), every public role the user holds in the group, highest first ({ GroupId, IsMember, Roles = { { Id, Name, Rank } } }), and PlayerService:HasGroupRoleAsync(target, groupId, roleIdOrName). See Friends, groups and ownership.

  • Changed

    PlayerService:GetGroupRankAsync, :GetGroupRoleAsync and :IsInGroupAsync use GroupService:GetRolesInGroupAsync instead of the deprecated Player:GetRankInGroup and Player:GetRoleInGroup. Rank and role are those of the user's highest public role, as before, and membership comes from Roblox's own IsMember. A user can hold several roles in a group; GetGroupRolesAsync lists them all.

  • Changed

    PlayerService:GetHumanoidDescriptionAsync and PlayerService:OwnsAssetAsync use Players:GetHumanoidDescriptionFromUserIdAsync and MarketplaceService:PlayerOwnsAssetAsync instead of the deprecated calls.

  • Changed

    PlayerService's platform and screen info no longer probe UserInputService:GetPlatform, VRService.VRDeviceName and GuiService:GetScreenResolution. Scripts cannot call them, so those probes only ever failed. ScreenWidth and ScreenHeight are the viewport size and VRDevice is nil, as they already were in practice, and the detection confidence is never "Reported".

  • Fixed

    DestructionService: a part that one client breaks locally (without authoritative) only kept its collision on the server in games that report Workspace.AuthorityMode = Automatic. Scripts cannot read AuthorityMode, so the server always turned the part's CanCollide, CanQuery and CanTouch off, and server raycasts and NPCs went through a wall that was broken on one screen only. The server now looks at the reporting player's character: when that client moves its own character (classic games), the server part keeps colliding; when the server moves it (Server Authority, or a game that keeps characters owned by the server), the part is released as before. See Client breaks and Server Authority.

  • Fixed

    DestructionService could not break anything whose textures together are wider or taller than 1024 pixels, and a single 1024 by 1024 texture was already too much. The shard texture atlas is now packed into one EditableImage of at most 1024 by 1024 pixels, and textures are scaled down when they do not fit.

  • Changed

    DestructionService shards look like the part they came from. A MeshPart's SurfaceAppearance carries over, with its color map, Color tint, AlphaMode and its normal, metalness, roughness and emissive maps. A transparent texture or decal shows the part's color underneath instead of white, decals keep their Color3 and Transparency and stack by ZIndex, pixelated textures stay pixelated, a textured MeshPart keeps its Transparency, and the cut faces show the texture of the part they were cut from instead of the whole atlas squeezed together. A texture the game may not load no longer fails the break: those shards use the part's color, and one warning names the asset. See What the shards look like.

  • Added

    GeometryService:SliceData(data, plane, capRegions?) takes an optional list of UV rectangles. A cut face whose edge lies in one of them gets its UVs inside that rectangle. See Working with mesh data.

  • Added

    ResampleMode on EffectService decal definitions: Decals = { { Texture = ..., ResampleMode = Enum.ResamplerMode.Pixelated } }. See DecalDef.

  • Added

    The ScreenInsets setting in UIService's configuration, an Enum.ScreenInsets name for every screen's ScreenGui (empty keeps following ScreenIgnoreGuiInset), and ScreenInsets on a layer definition (:DefineLayer or the Layers setting), so screens and layers can respect DeviceSafeInsets. See Screens and dialogs.

  • Changed

    PasswordSystem:Parse and :Verify reject a PHC string with an empty parameter (m=19456,,t=2) instead of skipping it, and a UIService theme token path with an empty segment ("Colors..Text") no longer resolves.

Updating from 1.1.1

Two changes can ask something of your game.

Paths in profiles. profile:Set and profile:Update no longer create missing tables. A write to a path whose table does not exist yet now errors and names the missing part, such as Engine: DataService Profile:Set - Stats does not exist. Patch.Set follows the same rule.

Before (1.1.1)
profile:Set("Stats.Kills", 0) -- created Stats when it was missing
After (1.2.0)
if profile:Get("Stats") == nil then
	profile:Set("Stats", {})
end
profile:Set("Stats.Kills", 0)

Tables every profile needs belong in the profile store's template, so profile:Reconcile() adds them to profiles that were saved without them, and you never have to check.

MonetizationService's profile store. If you sell developer products and relied on MonetizationService picking your player profile store by itself, bind the store yourself before players join:

MonetizationService:BindProfileStore(PlayerData)

Or set ProfileStoreName in the MonetizationService configuration. Records already saved in that store are used again as soon as it is bound. Without a store, receipts are only deduplicated on each server, and Engine warns you.

Update modelWhen to change your game
Automatic updatesRight away. Every server started since the release runs 1.2.0.
Manual updatesWhen you click Update to v1.2.0 in the plugin. Until then your game stays on its current version and keeps working as it does.

1.1.1

  • Fixed

    Engine:GetSystem("ModerationSystem") no longer errors with "ShouldDeliverCallback is a callback member of TextChannel; you can only set the callback value, get is not available" in a game that uses TextChatService. ModerationSystem now sets ShouldDeliverCallback on every text channel without reading it first. Roblox never lets a script read a callback, so a ShouldDeliverCallback your game sets on a channel itself is not kept next to ModerationSystem's: whichever is set last wins. Put your own message checks in ModerationSystem:AddChatFilter, or set ChatModeration to false in the ModerationSystem configuration to keep your own callback. See Chat moderation.

  • Fixed

    ModerationSystem:UnmuteAsync, ModerationSystem:UnbanAsync and ModerationSystem:LiftAsync resolve with the sanctions they lifted, also when the player is in the server. For a player in the server they resolved with an empty list, although the lift itself worked.

  • Fixed

    Reading NetworkService.OnServerInvoke or NetworkService.OnClientInvoke no longer errors with "OnServerInvoke is a callback member of RemoteFunction; you can only set the callback value, get is not available". Both return the function you set, or nil when none is set, on the server and on the client.

1.1.0

  • ChangedBreaking

    Engine:GetService(name), Engine:GetSystem(name) and Engine:GetPackage(name) return the service, system or package itself, on the server and on the client. The :Api() step is gone. The first call for a name loads the module, and prints its load time when LogServiceLoads is on in the General configuration; later calls return the same table.

The plugin was updated the same day. See the Plugin Changelog.

Updating from 1.0.0

Remove :Api() after every GetService, GetSystem and GetPackage call, in Scripts and LocalScripts alike:

Before (1.0.0)
local DataService = Engine:GetService("DataService"):Api()
local EconomySystem = Engine:GetSystem("EconomySystem"):Api()
local Promise = Engine:GetPackage("Promise"):Api()
After (1.1.0)
local DataService = Engine:GetService("DataService")
local EconomySystem = Engine:GetSystem("EconomySystem")
local Promise = Engine:GetPackage("Promise")

Studio's Find All / Replace All (Ctrl+Shift+F) changes every script at once: replace ):Api() with ). A script that stores what a getter returns and calls :Api() on it later needs the same change by hand.

Update modelWhen to change your scripts
Automatic updatesRight away. Every server started since the release runs 1.1.0, and code that still calls :Api() errors there.
Manual updatesWhen you click Update to v1.1.0 in the plugin. Until then your game stays on 1.0.0 and the old code keeps working.

1.0.0

The first release of Engine.

Services

17 services: AudioService, ComputeService, CryptographyService, DataService, DestructionService, EffectService, GeometryService, JunkService, MonetizationService, NetworkService, PerformanceService, PlayerService, SchedulerService, SpatialService, StatisticsService, TimeService and UIService.

Systems

4 systems: EconomySystem, LeaderboardSystem, ModerationSystem and PasswordSystem.

Packages

24 packages: BehaviorTree, Bin, Collections, Compress, Graph, Guard, Hash, Id, MathKit, Noise, Octree, Patch, Pool, Promise, Rand, RateLimiter, Serde, Signal, Spline, Spring, State, StateMachine, TableKit and Timer.

Configuration and updates

  • Per-module configuration with defaults, and automatic replication of client-safe configuration.
  • Automatic updates: a small shell loads the newest published engine when each server starts.
  • Manual updates: a fixed copy of the engine stored in your place, updated from the plugin.

The Engine plugin was released the same day. See the Plugin Changelog.