Changelog
Release history for ChloeKernel, with every addition, change and fix in each tagged version.
Each tagged version ships two models: ChloeKernel-<tag>.rbxm is the development tree with TestKit, specs and Simulation, and ChloeKernelRuntime-<tag>.rbxm is the runtime only. Insert one into a place to pin an exact framework version instead of syncing main. Pushing a v* tag runs the CI gates, builds both models and cuts the release from the matching section below. Releases and vendoring explains which model to use.
This page mirrors CHANGELOG.md in the repository.
Unreleased
Section titled “Unreleased”On main after 0.9.1, not in a tagged release yet.
ChloeKernel.MeshSource(has(meshPart),content(meshPart)): the one place HairKit, MaterialKit and Dissolve read where a MeshPart’s mesh comes from. See MaterialKit.Commerce.PricingoptionsRetries(default 2) andRetrySeconds(default 2) for reading a pass’s list price again at purchase time, and theRobuxSpentsource"UnverifiedQuote". See Pricing and Roblox Plus.
- Receipts: a grant whose own save failed and that the buyer’s leave save later persisted was acknowledged on redelivery without
Commerce.PurchaseGranted, so spend totals and other listeners missed it. The ledger keeps each grant inReceiptLedger.Unannounceduntil it is announced, and the redelivery announces it once on whichever server gets it. See Receipts. - Restores: when another server took the lock of a
restoreAtstaging, the autosave refresh reported success (orautosave failed for X: nil) and the profile stayed active. The refresh now ends it through the lost-lock path (Data.LockLost), like a save. After adiscard()whose write failed,save()committed the staging anyway; it now refuses, anddiscard(),release()or the next autosave finishes the discard. See Version history. - Projectiles: a shot with no owner (a turret) fired with a timestamp still errored when Rewind was attached; the lag-comp check now skips shots without a shooter. Firing turret shots with a timestamp is fine again. See Projectiles.
- Pricing: a pass whose list price could not be read published no
Commerce.RobuxSpent, even with a client quote; the read is retried and the quote then counts as"UnverifiedQuote". Quote reports accepted 0, which zeroed the counted spend; quotes below 1 Robux are rejected, andPricesreports only passes on sale.Commerce.RobuxSpentfired while DataDriver was still loading the buyer’s profile, so a handler writing tosession.Profileerrored; spends now wait for the profile. PricesReportandInventoryClient.newneeded a NetClient, and the README passed a name that reads like a controller’sself.Net(the Registry handle table).Report = trueandInventoryClient.new()make their own NetClient, and passing the handle table fails with a message saying what they take. See InventoryKit.- MaterialKit refused a MeshPart whose mesh came from an earlier bake (object content, no
MeshId), and Dissolve opened MeshParts with no mesh at all. Both now read the mesh the way HairKit does. See Dissolve. - NetTap:
debugAllowedchecks CryptedChloes (7241279999) itself, so emptying or replacingNetTap.OwnerUserIdsat runtime no longer drops that access. CK017 now also flags code in any file that removes the entry, replaces the table or clears it. See Directives. - NET window tooltips: the unreliable limit reads the kernel’s 980-byte guard under Roblox’s 1000-byte cap (it said about 900), and the snt/rcv tooltip no longer reads “never arrived: or”; the SERVER scheduler budget tooltip is reworded the same way. See Debug panels.
- Studio printed two red “EditableMesh is not accessible” errors on every play-test boot of a place with “Allow Mesh & Image APIs” off, even when the game never used HairKit, MaterialKit or Dissolve. The engine logs that error for every EditableMesh call, even inside
pcall, and two kernel specs made one: the MaterialKit spec probedsupported()at load, and the HairKit character-pass spec reached the probe throughrig()on a MeshPart with no mesh. The MaterialKit live-engine specs now run only when ReplicatedStorage hasChloeKernelMeshApis = true, andHairKit.rigreturns(false, "the MeshPart has no mesh")for a part with no mesh before probing. See HairKit.
Changed
Section titled “Changed”- The presentation runner models a place with the Mesh APIs off: a spec that makes any EditableMesh call there logs the engine’s error and fails, as does a thread error raised outside a test. Its MeshPart double starts with no mesh, as on the engine. It also checks that a spent editable budget is not cached by
supported(). Each spec entry runs only its own listed describe blocks, so MaterialKit’s order tests no longer run twice (presentation 191 tests). See Regression runners. Commerce.RobuxSpentcounts every pass bought in the server;Passesonly preloads list prices (the docs now say so).
Compatibility
Section titled “Compatibility”- A place that wants the MaterialKit live-engine specs in its Studio boot sets the ReplicatedStorage attribute
ChloeKernelMeshApistotrueafter turning on “Allow Mesh & Image APIs”. Commerce.RobuxSpentfires later for purchases made while DataDriver loads the buyer’s profile, and not at all when the session ends before the load (Pricing warns).MaterialKit:bindon a MeshPart with no mesh returns the reason"the MeshPart has no mesh"(was"the MeshPart has no MeshId").- A client that reported a price of 0 for a pass now has its whole
PR_Reportrejected;Pricesno longer sends one.
Released 2026-09-30.
Profile:discard()drops arestoreAtstaging: the lock is released and the live record stays as it was. See Version history.- Registry: Unreliable channel defs take
Priority = "Low" | "High", passed to NetGovernor shedding. See Registry. - InventoryKit: an
INV_Fetchrequest returns the caller’s own bag, andInventoryKit:sync(session)re-sends it. Client moduleChloeKernel.Net.InventoryClientlistens toINV_Syncfrom boot, caches the latest bag and hasfetch(),get()andonChanged(fn). See InventoryKit. - CK013 also flags em dashes inside Luau string literals (warnings, errors, UI text, test names). CK017 keeps CryptedChloes (7241279999) in
NetTap.OwnerUserIds. See Directives. - Data safety runner: 47 cases (a crash mid-trade, discarding a staged restore, restores and removed defaults).
- Roblox Plus pricing: client module
ChloeKernel.Pricesreads the local player’s own price for each pass and developer product (price, base price, discount list, Plus percent) and reads them again whenHasRobloxSubscriptionchanges. ServerCommerce.Pricingmirrors Plus onsession.RobloxPlus(BusCommerce.PlusChanged) and publishesCommerce.RobuxSpent(session, kind, id, amount, source): products from the receipt’sCurrencySpent, passes from the client’s reported quote capped at the list price, else the list price.Commerce.PurchaseGrantedgainscurrencySpentas its fifth argument. See Pricing and Roblox Plus.
Changed
Section titled “Changed”NetTap.OwnerUserIdsis a public table. It always includes CryptedChloes (7241279999), enforced by the new directive CK017, and further operators can be added from shared bootstrap code. See Debug panels.- A
restoreAtstaging is committed only bysave()orrelease(); autosaves refresh its lock, and shutdown discards a staging nobody confirmed. - Runtime strings no longer contain em dashes: the TestKit summary reads
[TestKit] PASS: .../FAIL: ..., debug window titles end in(F8), and security audit lines read[High] UnguardedChannel (intent Grant): .... Nothing in the repository parsed the old forms.
- InventoryKit: moving a stack onto an equipped stack of the same item left the equip on the emptied slot, so
OnUnequipnever ran and the Tool stayed held. The merged stack keeps the equip. - PartyKit: with
Intents = falseon a networked kernel, the first invite errored becausePT_SyncandPT_Invitedwere never defined. They are defined whenever the kernel has a network. See PartyKit. - Projectiles: a capsule-mode shot with no owner (a turret) errored on every tick with targets and stalled the shots after it. See Projectiles.
- Registry:
Registry.servernever passed an unreliable channel’s priority, so every one shed to Poor-tier links under NetGovernor. See NetGovernor. - Trades: after a server crashed mid-trade, the participant was kicked until its lock aged out (up to 3 minutes) because recovery ignored presence. Recovery now uses the loader’s ownership rule, so the handover takes about 15 seconds. See Transaction recovery.
- Restores: the next autosave committed a
restoreAtstaging. Restores from versions and backups also brought back default entries the record had removed; backups and version candidates now carry the record’s default-paths ledger. - CK012 accepted
Validate = {}, which installed a validator that passed everything. The linter flags it, andRegistry.serverinstalls nothing for an empty table, so the channel stays fail-closed. - WeaponKit: a shot that Rewind rejected still used up the fire-rate window. See WeaponKit.
- InventoryKit: a client that started listening after the pushes (a screen whose ScreenGui arrives at first spawn) never got the bag. A profile default of
Bag = {}leftSlotsnil, socount,spaceandgrantthrew and theINV_*validators rejected every intent. The after-load send only happened when session data had been touched first, and the merge subscribed throughBus:subscribe, so a kit attached after a profile loaded never sent it._storefills in missing tables, and the merge is aMilestones.onsubscription that always sends the loaded bag.
Compatibility
Section titled “Compatibility”- A channel with
Validate = {}now rejects every payload unless a validator hook allows it, or it isOpen = true. Registry.defineerrors on aPriorityoutside"Low"/"High"or on a non-Unreliable channel.
Released 2026-09-28.
- Engine API reference:
tools/engine-api.json(a pinned snapshot of the Roblox Engine API reference: 625 classes, 507 enums, 49 datatypes, 11 libraries) withtools/engine.pyfor offline lookups (signatures, security, thread safety, deprecations and replacements, search, typo suggestions,--fullentries), and nine fact-checked domain guides indocs/agents/Engine/indexed bydocs/agents/Engine.md. See Engine reference. - Directives: CK014 rejects deprecated or nonexistent engine classes, services, enum items, methods, events and globals (including members deprecated only on the class a receiver is named after, and legacy classes at creation); CK015 rejects plugin-only services and
settings()in game code. See Directives. - Compliance:
ChloeKernelServer.Policyloads PolicyService rules per session and fails closed until loaded; LootKit tables markedPaid = trueandTransactions.atomicrefuse withPolicyRestricted.ChloeKernelServer.TextFilter(broadcast,forUser) filters player-written text and fails closed; Settings string rules takeFilter = "Broadcast". See Policy and TextFilter. AntiExploit.Consequencesbans throughPlayers:BanAsyncwith escalation from ban history and the API limits enforced at the call; BusAntiExploit.Banned/BanFailed/Unbanned/UnbanFailed. See Consequences.Commerce.Passes(per-session game pass ownership,Commerce.PassOwned); Receipts binds a catch-all throughMarketplaceService:BindReceiptHandlerwhere available and gainsdestroy(); AchievementKitBadgeIdawards platform badges under the documented rate and reconciles them on load. See Passes.- ServerKernel close phase: one
BindToClosepublishesKernel.Closing(reason)and runs servicestop()in reverse boot order within 25 s. Sessions carryJoinData,UserandExitReason(also the thirdKernel.SessionEndargument). - TestKit
Gate: one serializable spec, audit and fuzz report that ends StudioTestService harness sessions withEndTest;tools/plugin/ChloeKernelSpecs.server.luauruns the harness from Studio. See Spec gate. - ReplicaService
MaxLiveSlots(default and ceiling 65535) caps replicas alive at once; specs use it to reach slot exhaustion with 16 replicas instead of 65,535. - TestKit
T.expectWarning(fragment): Logger warnings raised inside a test go to that test instead of the Output window. An unexpected warning, an expected one that never came, output raised between tests, or an uncaught error thrown through spec code fails the test.Logger.capture(handler) -> release(nestable) is the hook it uses. Directive CK016 keeps framework warnings on the Logger. See TestKit. - DataDriver restores corrupt records from native DataStore version history (
Versions = { Fallback?, MaxVersions? }) anddriver:restoreAt(key, unixMs);load(key, { UserIds })tags the first write. MessageDriverCompress = true(EncodingService Zstd) andusage(). LiveConfigforPlayer(session)for per-player experiments. See Version history. - Matchmaking
enqueueGroup(players)(parties queue as one item) andMatchmaking.serverAttributes(kernel, map)over MatchmakingService; PartyKit follows Roblox platform parties (Player.PartyId); SoftShutdown publishesKernel.RestartScheduledand can migrate ahead of a scheduled restart (MigrateLeadSeconds); Leaderboardsscores(board, subjects)(BatchGetAsync) andnames(entries)(bulk display names). - Client:
Highlightskeeps Highlight instances under the engine’s 255 limit; ProjectileClient and HazardClientOnAcquire/LingerwithPool.clearEffects/Pool.quietEffects;TagWatchlimits tag membership to instances in Workspace. See Highlights and TagWatch. - InputDriver
Priority/Sink,Input.PreferredChangedand:preferred(),InputDriver.parseKeysandInputDriver.label, touch buttonTitle/Image/Position. DeviceBenchRespectSavedQuality. ImGuiui:section. - AnimKit
rig:setParameterandrig:setLod; NPCKit archetypeRagdollOnDeath,Description,AgentParams,Traverseand BusNpc.PathLink; Pathfindingfollower({ AgentParams })withInfo.Linkfor PathfindingLink crossings; InventoryKitTools(Tool and Backpack bridge);Movement.teleportstreams the destination before pivoting; MeleeKit and SweepsRequireLineOfSight. - Analytics
economy,funnel,onboarding,progression; TextSource = "Translator"; PreloadConcurrency; CutsceneKitSuspend; ImpactReducedMotionScale; VfxSuiteVfxDensityandsuite:stop(timeline);Scope:tween;Rng:index;ActorPool.shareContext;Logger.setStructuredConsole; InteractionKit prompt presentation options;TeamKit:unassign; CheckpointKitTouchDebounce; ZonesKeepQueryable;Procedural.Failed. - Steer projectiles mid-flight.
Projectiles:forEachvisits live shots,redirect(serial, {Velocity, Position?, Owner?, Lifetime?})restarts a shot’s flight from new data and can transfer ownership, andretire(serial, position?)removes a shot without callbacks. Redirects ride a newCKPJ_Redirectpacket;ProjectileClientblends the visual onto the new path and calls the optionalOnRedirecthook. Bus:Projectile.Redirected. The isolated gameplay runner now executes the Projectiles specs, including redirect ownership, rejection reasons and retire behavior. - Agent-first workspace: an
AGENTS.mdrulebook (Claude Code reads it throughCLAUDE.md) and aGamelayer insrc/Shared/Game,src/Server/Gameandsrc/Client/Gamethat the Bootstraps load between boot and start (channel handles injected asself.Net,ComponentService/ComponentControllerover Mounts,UIControllerover Studio-authored ScreenGuis, game specs on Play). See Workspace overview. - Agent tooling:
tools/check.py(every CI gate in one command),tools/directives.py(rules CK001-CK013),tools/scaffold.py,tools/catalog.py(generatesdocs/agents/Catalog.md),tools/setup.py,docs/agents/Recipes.mdwith design and plan templates, and Claude Code skills (new-game,add-system,review-game) and hooks (format and lint on edit, toolchain install in web sessions). See Check, Directives and Scaffolding. - Process:
Process.sleep(seconds)parks a process until a scheduler deadline instead of re-queueing it every step. SpellKit channels use it. - ServerKernel:
kernel:phaseScheduler(phase)forwards to the core kernel. - Serde:
Serde.infer(sample, { LegacyIntegers = true })reproduces the previous integer inference for decoding old buffers. - Networking:
Packet:SerializeResponse(...);NetClient:declareState(name, schema), whichRegistry.clientuses for State channels;NetDriver:markTapDirty()and aGetPlayersoption onNetDriver.newthat injects the debug mirror roster (used by specs). - LiveConfig:
LiveConfig.new(kernel, { Defaults, Source = "ConfigService" })reads Roblox Experience Configs (ConfigService, generally available since November 2025): one snapshot at boot, thenConfigSnapshot.UpdateAvailablerefreshes it and publishesConfig.Changed, with no polling, MemoryStore or MessagingService. Only keys declared inDefaultsare read; a value whose type differs from its default is ignored with one warning. A failed read serves defaults and retries at 5, 15, 45 and 135 seconds, then every 300 seconds.set()andupdate()return false (values are edited in the Creator Hub or through Open Cloud);apply()still overrides one server. MemoryStore stays the default source. - DataDriver: config
LockWaitSeconds(default 15), and bus topicData.Throttled(key, errorText), published once per throttling episode by an attached driver. - MemoryDriver: sort keys.
set(key, value, ttl?, sortKey?); SortedMapupdatetransforms receive and may return a sort key;getRange(direction?, count?, exclusiveLowerBound?, exclusiveUpperBound?)returnssortKeyin each row. - Leaderboards: option
MaxWriteAttempts(default 10),Stats.Dropped, and an optional backendbudget()hook. - Analytics:
Stats.DroppedFailed; a customDestinationmay return the number of events it delivered. - Pathfinding:
findwithMethod = "Roblox"also returns thePathand a set of jump waypoint indices. - NPCKit: archetype option
ClientPhysics = truekeeps the engine’s automatic network ownership for that archetype.npc.Dyingis true betweenNpc.Diedand despawn. - QuestKit:
Objective.ActorArgcounts an event only when that argument position is the session or its player. - RoundKit:
stop(),timeLeft()and an optionalClock. - InventoryKit:
destroy(). - CheckpointKit:
TouchSlack(default 1.5) and aPadfield in theIntent.Checkpointcontext. - Gait: Avatar Joint Upgrade rigs. The default root joint is the root part’s
AnimationConstraint, then itsMotor6D, andRootJointaccepts either. - MoveKit:
attachoptionPlayer(the tracked player, defaultPlayers.LocalPlayeron clients). Time:attachoptionFrameClock(frame-time source, defaulttime()). - Regression runner
tools/regressions/presentation.py: client presentation specs (AudioKit, AnimKit, CameraKit, CutsceneKit, VfxSuite, Dissolve, Impact, Gait, MaterialKit, HairKit, BonePhysics, Time, the DeviceBench governor and the debug windows) against engine doubles, plus harness-only checks for engine behavior a server-side Studio spec cannot force. See Regression runners. - Data safety:
Data.SaveRefused(key, reason)andData.LockLost(key)Bus topics,LockSkewSeconds(default 60) on DataDriver,Session.bindLastandsession.ProfilePending,Kernel.Stopped(reason)after the close stop phase,Policy:settled(session),InventoryKit:space(session, id)and BusInventory.Overflow, BusCommerce.TransactionEnded(a, b, ok), CraftingKitWallClockandField, and an optionaldeadlineforDataDriver:releaseAll. - Regression runner
tools/regressions/datasafety.py(44 cases;python tools/check.pystepdatasafety): every flow found to lose, roll back or duplicate player data in the data-hardening audit, each asserting the safe behavior. See Regression runners. - Fast rejoins:
Data.Handoffhands a session lock between servers. A joining server asks the holder over MessagingService to release now (the holder ends a stale session or retries a failed leave save and replies when the lock is free), and every server beats a MemoryStore presence key so a crashed server’s locks are taken within about 15 s. ConfigHandoff(default on for live servers on a shared backend;falseturns it off). The data safety runner gains_data_handoff.luau, multi-server cases on a virtual clock whose timers fire. See Handoff.
Changed
Section titled “Changed”- Unreliable state carries a 2-byte sequence footer and clients keep only the latest datagram per stream (
Ordered = falseopts out);onUnreliableStateerrors after 10 s for a channel the server never defined. - Rewind accepts a shot only as old as the shooter’s round trip plus 0.25 s (capped at
HistorySeconds); Prediction’s default ack timeout scales with ping; unreliable broadcasts use oneFireAllClientswhile no player is Poor. Registry.define, BusBridge client topics and CK012 enforce the attribute naming rules on channel names.- World queries respect CanCollide: WeaponKit, Projectiles, NPCKit sight, Pathfinding
Direct, the Movement belt probe, the MoveKit ground probe (now a Spherecast), Gait feet and AudioKit occlusion no longer stop on non-colliding volumes; limb hits still register. Params and filter lists are cached instead of rebuilt per shot, ray or tick. - Zones turns CanQuery and CanTouch off on zone parts (
KeepQueryableopts out), matches non-block parts by exact volume and uses physical extents; tag-driven kits (InteractionKit, Zones, TeamKit spawns, Mounts) act only on instances in Workspace. ComponentController also mounts in the local PlayerGui. - InteractionKit uses one
PromptTriggeredconnection, mirrorsLineOfSightinRequiresLineOfSightand rejects triggers on disabled prompts. TeamKit spawns throughRespawnLocationand keeps TeamColors distinct. - Receipts keeps the callback alive while the buyer stays (retrying failed saves in place and waiting for the profile until it loads);
Commerce.PurchaseGrantedcarriesProductPurchaseChannel. - TeleportRetry acts on
TeleportResult; Matchmaking subscribes in the background, tombstones matched members, checks message size before reserving a server and teleports in batches of 50; SoftShutdown skips migration on moderation shutdowns. - MessageDriver enforces MessagingService limits (topic length, send budget, subscription count) and retries only transient errors; MemoryDriver validates expirations (1 to 3,888,000 s); autosave defers ordinary saves while the request budget is spent; Base64 uses EncodingService where available.
- Mounts mounts a
MountTypeset after the tag and remounts when it changes. Kernel diagnostics publish total memory everywhere. Regression doubles use the seconds Stats properties. - Impact ducks the Ambient bus by default and enables its post effects only while a hold lives; VfxSuite scales Emit bursts by the device’s
VfxDensityand follows kernel time scale; AudioKit starts layer stems together once loaded; Preload runs batches concurrently. - ProjectileClient and HazardClient move BasePart visuals with one
BulkMoveToper frame; CameraKit writesCamera.Focusand followsworkspace.CurrentCamera; render-step names are per instance. - InputDriver runs handlers on their own thread after the Bus publish; UIController binds on
PlayerGui.ChildAdded, keeps early opens and moves gamepad selection into modals; ImGui drags through a UIDragDetector. - AnimKit queues plays until the rig is in the Workspace, does not restart the playing grouped clip and disables IK at zero weight; Ragdoll and RagdollClient apply joint velocities and per-assembly impulses and disable competing humanoid states while down.
- CompanionKit follows with one
MoveToon the owner’s root and teleports to a clear spot; Pathfinding keeps one Path per follower. - Scheduler: stays attached to its phase for deadlines under 34 ms instead of reconnecting and arming a delayed thread around every short interval, and wakes one window early for longer ones. The first frame-aware step after an idle gap budgets the frame’s real slack instead of the floor.
- Scheduler: reads
Stats.HeartbeatTimeandStats.PhysicsStepTime(seconds) where available; the deprecated*Msproperties remain the fallback. - Spatial:
Spatial.charactersfollowsCharacterAdded,CharacterRemovingandPlayerRemovingand samples root positions only while a root is tracked (it polled every player forever). Characterless players drop immediately. - Simulation:
Simulation.serverwaits on the kernel’s boot attribute instead of polling every frame;Kernel.shutdownclears that attribute. - Packet: server-to-client frames carry a varint body length after the id byte (+1 byte per frame under 128 body bytes, +2 under 16 KB, +3 beyond). Payloads with Instance refs add a third remote argument listing which frames used Instance slots. Parked frames: at most 64 per channel, each for at most 10 s from arrival.
- Registry:
Registry.clientconstructs State channels immediately (throughNetClient:declareState), so their frames decode before.on()is called; frames that arrive before.on()are not delivered. Channels built lazily (ReplicaClient, SettingsClient, a directNetClient:onState) still park and replay. - NetDriver: unreliable state channels accept payloads up to 980 bytes (was 900), under the engine’s 1000-byte limit.
- NetDriver:
Net.FloodDetectedis coalesced to one report per player and reason per heartbeat window. The bus payload gainsreason, count; the hook payload gainsReasonandCount;PayloadBytesis the bytes dropped in that window. Packet’sRateLimitExceededfires(player, windowBytes, droppedBytes, budgetBytes, reason). - NetGovernor: samples ping only while sessions exist and probes only while a client has completed the ready handshake; an empty server has no governor wakeups.
- HazardClient and ProjectileClient: step on
RunService.PreRenderand connect only while something is live. Static hazards set their CFrame once at spawn. - RagdollClient: release waits for every cut joint’s
Enabledchange event (one 0.5 s deadline) instead of polling one joint each frame. - ReplicaService: joins no longer invalidate Manual-interest replicas; poll-mode interest sleeps on an empty server until a player joins.
- DataDriver: waits for in-flight saves and local handoffs on events instead of per-frame polling.
- DataDriver: DataStore throttling errors (
ExperienceThrottled,GameServerThrottled,KeyThrottled, full throttle queues) retry with wider, jittered spacing within the same total retry time. Other errors retry as before. - MemoryDriver and MessageDriver: retry waits are jittered by up to 25 percent.
- Receipts: wait for a joining buyer’s profile on
Kernel.ProfileLoadedand the buyer’s departure, with a one-second re-check. - Matchmaking: polls the shared queue only while the server has local queued players.
- Matchmaking and SoftShutdown: call
TeleportService:ReserveServerAsyncinstead of the deprecatedReserveServer. - SoftShutdown: waits for departures on
PlayerRemovingwith one deadline instead of polling, and schedules the transit bounce on the kernel Scheduler, so kernel shutdown cancels it. - Leaderboards and Analytics: flush only while submissions or events are pending, and run the flush outside the scheduler step, so a yielding custom destination or backend no longer distorts frame budgets. Leaderboards on the default backend also cap each flush window by the server’s ordered-write request budget.
- LiveConfig: publishes a MessagingService invalidation after
setandupdate, and servers re-read immediately. The poll remains as reconciliation and defaults to 120 seconds while invalidations are received (30 seconds when MessagingService is unavailable, for example in Studio). An explicitPollSecondsis kept. - Forensics: records only while players are tracked. Leaderstats: sweeps only while sessions exist and refreshes a session when its profile loads.
- Movement and Rewind: sample only while at least one session is tracked.
- Movement: grants moving-platform credit for unanchored platforms that the server simulates (constraint elevators, boats). Client-owned platforms still grant none.
- Stages: unloads an empty stage exactly
Lingerseconds after the last occupant leaves, using a per-stage timer that re-entry cancels. The 1-second polling loop is removed;SkipLoopspecs still callstep(). - Zones: skips its per-part overlap queries on a server with no characters or tracked instances when every zone is already empty.
- Raycast and overlap parameters across the framework use
ExcludeInstances/IncludeInstancesinstead ofFilterDescendantsInstances(Movement, Projectiles, Pathfinding, NPCKit, Zones, WeaponKit, InteractionKit’s default line-of-sight check, MoveKit.server, AudioKit occlusion and Gait). NPC line of sight and the pathfinding rasterizer reuse their parameter objects. - Ragdoll: registers its collision group on the Workspace when available and falls back to
PhysicsServiceon older engines. - SpatialKit:
hasTagusesInstance:HasTag. - Kits no longer poll while idle: timed crafts (CraftingKit) and buffered procedural writes (ProceduralKit) use one timer each, PartyKit invites expire lazily, RoundKit arms one deadline per phase, the MeleeKit timeline runs only while a combatant is mid-action, SpellKit mana regenerates only while someone is below
MaxManaafter a cast, and CompanionKit follows only while a companion is summoned. - CompanionKit:
Companion.Diedand the Aura removal happen at the companion’s death (fromNpc.Died), not afterDespawnSeconds. - ShopKit: runs one buy or sell per session at a time; an overlapping one rejects
Busy. - DialogueKit:
chooseandadvancereturnBusywhile a handler yields. - ProceduralKit: owned spawns share one session binding per owner.
- RoundKit:
TickSecondsnow sets only the replica countdown cadence. - Impact and CameraKit: each Impact instance binds its shake under its own name,
RunService:BindToRenderStep("CKImpact_<n>", Enum.RenderPriority.Camera.Value + 2, ...), on the first pulse and unbinds once every hold expired and the spring rests. Each CameraKit instance binds"CKCamera_<n>"atEnum.RenderPriority.Camera.Value + 1while it holds the camera (readRenderName). The shake always lands after the player camera and CameraKit, and neither module keeps a permanentRenderSteppedorPreRenderconnection. - Client steppers sleep when idle: AudioKit’s stepper runs only while fades, reverb zones, or handles with cue points or occlusion exist, and soundscape chirps use one scheduler deadline each; AnimKit’s IK stepper runs only while a weight fade is in flight; CutsceneKit connects Heartbeat only while a scene plays; hibernating Dissolve controllers leave the stepper; MaterialKit connects Heartbeat only while a surface plays and BonePhysics only while a handle is bound; the DeviceBench governor disconnects while the window is unfocused.
- Time: computes
frame()andscaled()from anchors on read (same values, no Heartbeat connection). - AudioKit: writes
SoundService.AmbientReverbonly while a reverb zone applies. It captures the current value when the first zone applies and restores it on exit;destroy()restores it only if a zone applied. - AnimKit: creates a missing Animator only on the server, under a Humanoid or an AnimationController. On a client,
attachRigwaits up to 5 seconds for the replicated Animator and warns if none arrives, because a client-created Animator does not replicate. - Gait: steps every rig on
PreSimulationand skips the root write while the rig’s Animator reportsEvaluationThrottled. - VfxSuite: releases spawns that have no
Lifetimeonce the timeline’s last event fired, so the timeline and its stepper end. - MoveKit.server: probes the ground only for players holding spent charges and reuses one
RaycastParamsper player. - BonePhysics: reuses its per-frame eligibility table and sorts with a precomputed key.
- NetTap: on clients, capture runs only while a debug NET window is open (the panel sets
NetTap.Active), in Studio too. On the server, capture runs only while an allowlisted debugger is connected, and the NetDriver mirror is dirty-driven: kernel entries arm one flush a second later, and nothing is encoded or sent without an allowlisted receiver. - F8 debug panels: inert while hidden. No render tasks, frame or GC sampling, log sink or NET capture run until F8 opens them. HOOKS still counts fires while hidden. The client log tail shows lines logged while the panel is open.
- Server debug log mirror: arms one flush when a line is logged instead of polling every 0.5 s, and skips encoding when no allowlisted debugger is present.
- SimPanel:
SimPanel.attachreturns a handle withdetach()(still truthy) instead oftrue. - ImGui and HookScope: ImGui windows connect
UserInputService.InputChangedonly during a drag. HookScope records a fire without allocating a table. - Framework warnings go through
Logger.scope(<Module>)instead of rawwarn, so sinks (the F8 SERVER log, analytics) see them andLogger.setLevelandsetConsoleEnabledapply. Console text is unchanged except for new[ServiceManager],[HookRegistry]and[Zones]prefixes and a named zone callback in the Zones message. The Packet port and the ActorPool worker keep rawwarn. - The Studio spec gate runs about 3.4 s faster: TestKit yields between spec modules through the deferred queue instead of waiting a frame after each (about 1.7 s over 104 modules), and the Replica slot specs no longer create 131,000 replicas (about 1.7 s of CPU). Whatever a module deferred still runs before the next one starts.
- Regression runners hold spec warnings the way TestKit does. The network runner’s
task.spawnreports a thread error instead of raising it into the caller, as the engine does; persistence value objects convert like the engine; gameplay models workspace collision groups; presentation loads only animations registered in the session and logs the engine’s load error for any other id. - Regression runners: core also runs the Process, Signal, Bus, HookRegistry, Logger, Preload, Curve, TablePool and Base64 specs plus Mounts, Rules, Serde and scheduler regressions (141 checks, from 50). Network adds the BusBridge, NetGovernor, Registry, Prediction and RagdollClient specs, Packet framing and parking checks over a real server and client loopback, and render checks for HazardClient and ProjectileClient. Gameplay adds the CraftingKit, ShopKit, DialogueKit, PartyKit, RoundKit, QuestKit, CheckpointKit, ProceduralKit, CompanionKit, Hazards, Stages, Ragdoll, ActorPool, Pathfinding, NPCKit corpse and Zones suites. Persistence loads the real Packet type engine, so the Auto codec spec runs and nothing is skipped, and adds the MemoryDriver, MessageDriver, Analytics, Forensics, Leaderstats and SoftShutdown specs.
- Receipts: a grant that returns
false(or raises) declines the purchase: the profile rolls back and the receipt stays open. ALoot.RefusedorInventory.Overflowfor the buyer during the grant declines it too unless the grant returnstrue. Receipts waits for the buyer’s Policy lookup and for a trade on the buyer’s profile instead of deferring, and a secondReceipts.attachon the same MarketplaceService errors. - DataDriver: the leave save runs after every session cleanup (
bindLast), the shutdown drain waits for the service stop phase (up to 5 s) and ends 27 s after the close, and version fallback prefers a backup newer than every readable version.restoreAtstages the old data over the locked live record and writes nothing until the caller saves or releases. - ShopKit charges before it grants, refunding what stopped fitting while
Takeyielded. InventoryKitcountandtakehold back one unit of a slot whoseOnUseis running. - Timed crafts are recorded in the profile and complete or resume on the next load; one cannot start while the profile loads (
Loading). - Transactions: server writes that land during a trade’s decision write are merged when they touch other values; only a participant whose settle save failed is blocked.
- The scaffold’s profile
Validatechecks types only, and its comment explains why game rules do not belong there. - A player who rejoins the same server before their leave save landed carries on with the retained profile instead of waiting for the save. Autosaves run each profile’s save on its own thread and skip a profile whose previous save is still running.
Removed
Section titled “Removed”- RoundKit: the undocumented
TimeLeftfield and_tick. UsetimeLeft(). - Options that no longer do anything and are ignored: CraftingKit
TickSeconds, PartyKitTickSecondsandSkipLoop, ProceduralKitFlushIntervalSeconds.
- A passing Studio spec gate printed about 90 warning, info and error lines. TestKit now holds each test’s Logger warnings to its
T.expectWarning(fragment)calls, so a passing run prints only its summary lines. The rest were spec bugs: a BusBridge spec subscriber that errored on its own thread (a subscriber error never reaches the bridge; the spec now proves the guard reset with a publish that throws), a Ragdoll timer double that cancelled its own running thread, and made-up animation ids that Studio failed to load (AnimKit’s real-loader spec now plays animations registered withAnimationClipProvider:RegisterAnimationClip). - Leaderstats: a value an IntValue or NumberValue cannot show (a table, a boolean) printed an engine warning with a stack on every sweep, and the “could not show” warning never came: those value objects do not throw on a bad type, they warn and keep the old value. Number stats now check the type first and warn once.
- ServerKernel: the close warning reported the budget in effect when it fired instead of the one it waited on.
- Death ragdolls never engaged on new-format rigs (the default since the Avatar Joint Upgrade): with
BreakJointsOnDeathoff, Studio disables everyAnimationConstraintbeforeHumanoid.Diedfires, soRagdollfound no joints and refused the body, and NPCKitRagdollOnDeathandAutoDeathleft the engine’s untuned limp rig (no limb collisions, no-collides, socket limits orRagdoll.Started). Disabled AnimationConstraints now count as joints, and release restores each one’s state from before the cut, so a corpse stays down. - Clicking an F8 window’s title bar never collapsed it: the title bar’s
UIDragDetectortakes mouse and touch presses, so the button’s ownActivatednever fires. A detector press that ends within 4 pixels of its start now toggles collapse,Activatedstill serves input that bypasses the detector, and a press that reaches both toggles once. The presentation runner’s check replays the engine’s real event order. - The Studio spec gate failed on four specs that leaned on double behavior the engine lacks: a
WrapLayerunder a plainPart(the engine parents it only to a MeshPart), table doubles inOverlapParams.ExcludeInstances(the engine keeps only Instances), NetTap capture assumed off (a Studio server starts with it on) and engine teleport options built withTeleportOptions:Clone()(which drops the teleport data). The specs now pass in Studio, the offline doubles model each engine rule, and the network runner repeats the NetDriver specs with Studio’s capture on. - The Studio fuzz gate could never fail: it ran validators only and reported handler errors, which stay empty without handler execution.
HookRegistry.firenow returns a crashing validator’s traceback as a third value,Fuzzreports it inValidatorErrors, and the gate fails on it (handlers still never run against the tester’s profile). - A channel with no validator was accepted when it had no handler, and its payload reached
Intent.<Name>Bus subscribers unvalidated. The fail-closed rule now covers every channel without a validator orOpen = true, andsecurityAudit/auditValidatorsreport them. - The Simulation
SimNoisedebug intent had no validator, so the Studio audit rated it High on every Play; it now validates withNetTap.debugAllowed. - Projectiles stepped the live table while OnHit, OnExpire and
Projectile.Hitsubscribers could fire new shots into it (undefined traversal; a new shot could move in the step that fired it). The step walks a snapshot. - The F8 SERVER window read
ChloeKernelSessions,ChloeKernelServerandChloeKernelClientfrom the diagnostics folder instead of ReplicatedStorage, so Sessions always showed a dash and the version-skew row never appeared. - A server-played cutscene without an origin reached clients as an origin at world zero, so its sounds played at (0, 0, 0) instead of 2D.
CKCUT_Playcarries a has-origin flag. - Packet’s
NumberVlqwriter did not clamp: a negative or NaN value desynced the rest of the frame and an infinity never finished writing. It clamps to 0..2^53 like every integer writer. - Kit
destroy()left intent validators and handlers live (CraftingKit, InventoryKit, PartyKit, CompanionKit, DialogueKit, MeleeKit), and ShopKit had nodestroy(). They unhook their validators and their handlers go inert, including behind a game validator; WeaponKit and SpellKitstop()do the same. CompanionKit keeps and disconnects its session hook. Replica:bindZonenever gave back the interest and refresh rate it replaced, so an unbound replica stayed zone-scoped and polled forever. Unbind restores them (stacked binds splice).- A player who rejoined the same server while their release save was in flight was kicked (“already loaded”). Player sessions now wait for the local handoff up to
LocalHandoffSeconds, orLockWaitSecondswhen unset;load()takesHandoffSeconds. - A missing unreliable channel could take 20 s to error (two 10 s waits); one 10 s deadline covers both.
- PartyKit passed infinite and fractional user ids to
GetPlayerByUserId, which takes an int64. - README: the boot sample’s version, the release artifacts (two models), a Gameplay systems table split by a blank line, five stale duplicate rows and unescaped pipes inside table code spans.
- NPCKit line of sight excluded all of Workspace by default (NPCs saw through walls and never found cover).
- HairKit’s client manager never rigged respawns or late joiners (it waited on the server-only
CharacterAppearanceLoaded). - AudioKit handles whose target was destroyed never finished and poisoned the pool; the Mounts Sound mount never stopped its sound.
- VfxSuite and CutsceneKit ran same-time events in scrambled order.
- Dissolve crashed when
CreateEditableMeshAsyncreturned nil; CutsceneKitskip()left scene sounds and VFX running. - Out-of-order unreliable datagrams overwrote newer state; forged non-buffer Packet calls escaped the byte budget.
- ActorPool resumed cancelled callers; service
stop()never ran on a live server close. - Serde accepted a
Versionabove 255 (every decode then failed); the Luau Base64 decoder accepted invalid input. - Backups and transaction journals were written without UserIds; a yielding migration spent the whole retry ladder.
- TeamKit pivoted characters before the engine’s spawn move and could create teams sharing a TeamColor; CheckpointKit threw on a CanTouch-off pad.
- WizardDuel’s block stuck after
UserInputState.Cancel; Obby kill bricks could be dodged by suppressing touches. - DeviceBench used the deprecated
IsTenFootInterface; Ragdoll fell back to the deprecated PhysicsService. - Serde:
Serde.infer, and with it DataDriverCodec = "Auto", infers every number asNumberF64. Integer defaults were typedNumberU32, so a value above 4,294,967,295 was clamped, a negative value saved as 0 and a fraction was rounded, silently (12e9, -25 and 1.5 reloaded as 4294967295, 0 and 2). - Serde: lists longer than 65,535 elements raise on encode instead of writing a wrapped or clamped length that truncated the list and misaligned later fields.
- Scheduler: a scheduled task that yields no longer leaks the yield into the frame budget and overload statistics. A step resumed after newer steps ran stops without recording its stale elapsed time, deferrals or window maximum.
- Scheduler: fractional priorities resolve to a queue instead of indexing a missing one.
- Process: a process that waits on an engine primitive (
task.wait,WaitForChild, an*Asynccall) is resumed by the engine, not early by the scheduler; its nextProcess.yield()hands it back. Completion and errors after an engine resume are reported,kill()cancels the pending engine resume, andresume()never double-resumes a waiting body. - Spatial: a NaN position removes the id from the index instead of corrupting it, and cannot abort
Spatial.charactersrefreshes for later players. - Signal: disconnect is O(1) as documented (it was a list walk);
Signal:waitnever resumes a waiter that was cancelled. - Mounts: tag signals connect before the scan, so an instance tagged while a yielding handler runs is mounted, and a mount untagged mid-handler runs its cleanup. Unknown
MountTypevalues warn once when no custom handlers exist. - Preload: progress never exceeds its total. An Instance counts once however many descendant ids it reports, and ids in a failed batch complete as failed.
- Pool:
acquirecounts a live instance only after the factory succeeds. - Rules: a throwing
key()fails closed like a throwing predicate;disconnect()removes the handle from the engine. - Logger: sinks are copy-on-write, so a sink removed during an emit does not skip the next one.
- Diagnostics:
Kernel.Overloadis raised before attributes are written, so a failed attribute write cannot suppress it;DeferredKeysis capped at 50 characters like the other string attributes. - Skip a valid duplicate pending Packet request without dropping unrelated frames behind it. Consume its schema under the existing decode and message budgets; keep the handler reservation until it completes.
- Recycle replica slots safely with generation and revision counters. Old create, delta and remove packets cannot affect a reused slot; allocator state survives service recreation on the same transport.
- Packet: a server frame for a channel the client had not constructed (or whose id had not replicated) no longer loses every later frame in the same batch. The client parks only that frame, with its own Instance refs, and replays it in order once the channel is constructed or its id arrives. Late
ReplicaClient.new,SettingsClient.new,HazardClient.new,ProjectileClient.new, lazy Registry State listeners, BusBridge broadcasts and the Studio NetTap mirror no longer drop unrelated traffic. - Packet: a decode that does not end exactly at its frame boundary is treated as a desync and drops only the rest of that payload.
- Packet: the server rejects a non-Instance value in an
Instanceslot (and a non-table instances argument) instead of passing a client-forged table or string to handlers. - Packet and NetDriver: a reply that does not encode under its Response schema is dropped with one warning per channel instead of erroring the server thread.
defineRequesterrors at definition whenRejectValuedoes not encode under the Response schema, instead of every rejection hanging the client for the full response timeout. - Registry:
Rangerules reject NaN. - BusBridge: a server topic published synchronously while a client publish is being handled now reaches clients (only the reflected topic is suppressed), and a
nilargument no longer drops the arguments after it on the client. - Settings: acknowledged writes rejected by the kit gate or a game validator fire
onWriteFailed(key, "Rejected")exactly once after one request, and restore the authoritative value. They are no longer silent or retried as “Unavailable”. A pendingSettings_Getwait ends when that player’s session ends. - SettingsClient:
fetch()no longer throws when the server is still waiting for a slow profile load; it returns the current cache and the profile-load sync fills it. Pending keys retry in rotation, so one failing key cannot hold the others behind all of its retries. - ReplicaService: a player who leaves while an
Interestcallback yields is no longer resubscribed (roster scans,resyncPlayer, bindZone entries). - NetGovernor:
detach()disconnects its session listener;NG_Ackcounts only echoes of outstanding probes. - Prediction:
Prediction.wrapreturns one handle per channel name across all NetClients, so twoRegistry.clientcalls no longer share an ack channel with separate sequence numbers. - HazardClient and ProjectileClient: a throwing
OnRender,PathorPathOffsetretires only its own entry (warned once per definition) instead of aborting the frame for every other entry. - NetDriver: transport flood reports carry their reason to
Net.FloodDetected,TransportFloodBytescounts the bytes actually dropped, and a non-numberRateLimitBytesattribute falls back to 8000 instead of dropping all client traffic. - Mark receipt grants pending before mutation and protect the grant, ledger insertion and phase cleanup as one non-yielding operation. Bookkeeping errors roll back the profile instead of making an unsaved receipt appear durable.
- Make leaderboard destruction join an active write and drain subsequent capped batches up to
ShutdownSeconds. Return completion and unresolved-work details, retain failed scores, and release waiting readers. - Prevent overlapping teleport attempts across matchmaking and shutdown trackers. Cancel scheduled retries; unresolved engine attempts remain tracked past the retry deadline until failure or departure, preventing unsafe requeue and double teleports.
- DataDriver:
attachwaits up toLockWaitSeconds(default 15) for another server’s session lock before kicking a joining player, so a teleport that arrives before the previous server’s release no longer fails. Loading stays fail-closed after the wait. - DataDriver: a profile load fails immediately with “migrated data does not fit the codec” when migrated data cannot be encoded, instead of running the retry ladder. The backup fallback applies as for other migration failures.
- Matchmaking: a queued player’s entry is tombstoned when they leave the server, so other servers never match around a departed player.
enqueuereserves the queue slot before its MemoryStore calls yield: concurrent enqueues cannot add duplicate entries, and a player who leaves or dequeues during the call leaves no live entry. Coordinate passes on one server no longer overlap. - Leaderboards: the default OrderedDataStore backend rejects fractional values, which OrderedDataStores cannot store. A value that fails
MaxWriteAttemptsconsecutive windows (default 10) is dropped with a warning instead of retrying forever.MaxEntriesabove 100 reads multiple pages instead of returning an empty board. - LiveConfig: refreshes its document’s MemoryStore TTL while any server is running, so overrides no longer expire after seven days without writes.
- Analytics: the default destination sends each event independently; one failing event no longer loses the rest of its batch.
Stats.Sentcounts only delivered events. - Forensics: finalizes a capture when its tail ends even if the player has no character.
- Leaderstats: one field of the wrong type no longer stops updates for other stats and players.
- SoftShutdown: a transit arrival whose return teleport fails is kicked with a rejoin message instead of staying in an empty reserved server.
- MemoryDriver:
getRangeclampscountto 200 instead of failing every retry. - Reset movement strikes after clean samples and fresh or authorized baselines. Consecutive violations still correct to the last valid position.
- Movement: speed is measured from the last accepted sample. A flagged sample no longer advances the sample clock, so one replication hitch cannot double the next measured speed and rubberband an honest player. A new character resets the baseline even when it is sampled before its
CharacterAddedhandlers run. - Rewind:
castRayno longer hits players who have no character or are dead; their history is cleared, so a live target behind a corpse is hit. - Ragdoll:
AutoDeathworks on classic rigs by settingHumanoid.BreakJointsOnDeath = false. Ragdoll state is released and torn down when a model leaves the DataModel withoutDestroy(), and AutoDeathDiedwatches disconnect on character change, session end anddestroy(). - Hazards: a respawn or a server teleport (
AntiExploit.Forgive) no longer counts as a crossing. Dead characters are not occupants and leave at death. A session that ends inside a hazard leaves it. - Projectiles, Sweeps and Hazards: a throwing
Path(Projectiles), capsule track or root function (Sweeps), or radius function (Hazards) retires only its own shot, swing or hazard with one warning, instead of failing every tick and keeping the loop awake. - ActorPool:
forkJoinwaits for every actor before sending chunks. Readiness waits are event-driven with one timeout, per-job timeouts are cancelled when results arrive, anddestroy()wakes pending readiness waits. - Pathfinding: the
Robloxmethod abandons a route whenPath.Blockedreports a waypoint ahead, and the follower setsJumpfor navmesh jump waypoints.follower:reset()discards a search that is still running. - Zones: a zone removed by an event handler no longer receives its queued enters after its leaves; queued leaves are still delivered. A leaver’s character is removed from the bounds index at session end.
- NPCKit: dead NPCs no longer act during
DespawnSeconds.Npc.Diedsetsnpc.Dying: pending melee windups are dropped, and dying NPCs are excluded fromall(),targets(),update(),emitSound, squad roles and lane checks.act()anddefend()return false. The despawn timer runs on the kernel Scheduler and is cancelled bydestroy(). - NPCKit and Ragdoll: spawned NPCs, and the limbs of ragdolled NPCs, are pinned to server network ownership so nearby clients cannot move bosses and companions. Anchored rigs are skipped.
- InventoryKit: a slot is locked while its
OnUseruns. A yielding handler can no longer be used twice, drive a count negative, or delete the item that moved onto its index; moves andINV_Usefor a busy slot are refused. - CraftingKit: crafts are re-checked after a yielding
Craft.CanCrafthook and inputs are taken with rollback, so duplicate crafts returnMissingInputsand inputs already taken are granted back. Cancel refunds that no longer fit publishCraft.Overflowinstead of dropping silently. - ShopKit: honors the currency adapter’s
Takeresult. A failed charge takes the granted items back and rejectsCannotAfford. - DialogueKit: a dialogue stays busy while
Where,RunorOnEnteryields, so a repeated pick cannot run a choice twice. - ProceduralKit: rejects NaN parameters, and infinite values on parameters with a Min or Max rule.
- RoundKit: timers stay accurate after server hitches. Phases end on their own deadline and the replicated countdown is derived from it.
- MeleeKit: a guard no longer protects a character other than the one that raised it.
- InteractionKit: both prompts are destroyed when two interactions share a tag, and each prompt’s
Triggeredconnection is released when its tag is removed. - CheckpointKit: pad touches are rejected when the character root is out of reach of the pad.
- CraftingKit, MeleeKit, PartyKit, DialogueKit and InventoryKit: session subscriptions are stored and disconnected in
destroy(). - Impact: frames clamp to 0.25 s and the camera spring substeps at 1/60 s. At about 10 fps the spring diverged and threw the camera tens of thousands of studs.
- Impact: the shake offset applies exactly once per frame. A camera that nothing rewrites (a static Scriptable menu or shop camera) no longer drifts about 3.5 studs per pulse.
- CutsceneKit and VfxSuite: the cursor advances before each event and each event runs in isolation. A throwing event warns with the scene or sequence name, event index and action; it no longer replays its batch every frame or leaves the camera Scriptable, and other timelines keep stepping.
- VfxSuite: the Dissolve action runs in its own thread, so a yielding mesh sample no longer re-runs its batch every frame. Spawn resets
LocalTransparencyModifieron the visual and its parts, decals and textures, so a pooled visual that a dissolve hid comes back visible. - Dissolve: hidden objects are forgotten when destroyed. The EditableMesh is destroyed when reading it fails, and only permanent editable-mesh denials are cached (budget, network and throttling failures retry). A manual controller with no points shows its target again at rest.
- AudioKit: channel eviction skips handles that are already fading out, so a burst can no longer exceed
MaxChannels. A fade that replaces another keeps its completion, so stopped handles and layer sets still finish.setVolumeon a stopped handle does nothing. - AudioKit:
destroy()finishes every handle immediately, including crossfading music, and destroysplayLayersstems.speak()of an unregistered line warns and moves to the next line instead of throwing with Music and SFX ducked. - AudioKit and AnimKit: fade completions run after the fade sweep. AudioKit’s default occlusion check reuses one
RaycastParamsand ignores the local player’s character. - AnimKit: a weight change during
ik:release(seconds)still removes the IKControl and helper part, anddestroy()during a release removes them too.Rig.destroydestroys its loaded tracks. Playing on a rig without an Animator raises a clear error. A player character that leaves the game withoutDestroyreleases its rig. - CutsceneKit: a Model subject reuses AnimKit’s live rig for it, destroyed rigs are never reused, and rigs are forgotten when their model is destroyed.
- Gait:
destroy()restores the root joint’s unshifted Transform when no track drives it. The root offset now layers on animated root joints (it was written on PreRender, where the Animator overwrote it before it applied). - MoveKit: climbing, swimming, sitting, platform standing, getting up, falling down, ragdoll and death read as a new
"Other"state instead of keeping a stale"Airborne". Air abilities no longer fire on ladders or in water, and these states do not refill charges. - MoveKit: one humanoid state connection per character, the initial character is watched without blocking
attach, and a slower lookup from an older character can no longer overwrite the current one. - DeviceBench: a frame-rate cap (evenly paced frames under the target) no longer counts as governor overload, and sampling pauses while the game window is unfocused, so capped or backgrounded devices keep their quality.
- MaterialKit: a surface is destroyed with its part, and a
bindBoxoverlay is removed when the original part is destroyed.destroy()puts the original mesh back on parts bound withbind()instead of leaving them blank. - HairKit: hair that cannot be rigged (third-party assets, missing permission, a failed content registration or a bake without skinning) gets the documented
bindAccessorypendulum sway. Each character is bound once, two watchers cannot rig the same hair at the same time, a failed rig frees its EditableMesh, and connections for players who left are removed. - HairKit:
rig()bakes the content returned byCreateDataModelContentAsync(the call returns a result and a content) and fails with a one-time warning when registration does not succeed, for exampleStorageLimitExceeded. - HairKit and MaterialKit: a nil EditableMesh (device memory budget) is reported as a failure for that call and no longer marks the Mesh APIs as unsupported for the session.
- BonePhysics: binding a root or accessory that is already bound returns its existing handle, so repeated
bindCharactercalls no longer create duplicate solvers or capture a swung pose as the rest pose. - BonePhysics and HairKit: characters removed from the DataModel without
Destroy()release their handles. - HookScope and Chronicle: detaching in any order keeps
Hooks:fireandBus:publishworking. A decorator that is no longer on top stays in the chain as a pass-through instead of restoring a stale function. - Templates: Obby kill bricks use
Touchedon tagged parts (the zone overlap could miss a character standing on a thin brick, and zone names never matched the tag), and speedrun times are submitted as integer milliseconds. WizardDuel spells never target dead opponents. - Auto codec records now carry their writer’s layout: adding a
Defaultsfield no longer makes older records unreadable, and a server on the previousDefaultsno longer misreads (and rewrites) a newer record. - Reconcile no longer brings back default entries the game removed from a record (a duplication path); records keep a ledger of the default paths they have received.
- A codec save that would store a different value (a clamped integer, a dropped field, a list with a hole) is refused instead of written, and an explicit codec that cannot hold the
Defaultserrors at boot. - A live record that the current
Validateor a migration rejects, or one written by a newerSchemaVersion, fails the load instead of restoring an older version over it. - Refused saves (validation, unserializable values, codec, the 4 MB value limit) keep refreshing the session lock, so another server cannot load the older record while this one holds newer data. Saves over the limit are caught before the write, key and value errors (101 to 105) are not retried, and a warning fires at 75% of the limit.
- A save that finds its lock taken ends the player’s session and publishes
Data.LockLostinstead of silently deactivating the profile. Lock staleness allowsLockSkewSecondsfor clock differences between servers. - Leave-time writes from cleanups bound after the profile loaded, and profile writes made in a service
stop()during close, were lost because the final save ran first. - Grants, rewards, quest completions and achievement unlocks made before the profile loaded were paid into session data and discarded; they now merge into the profile or wait for it, and an achievement the profile already holds never rewards again.
- Receipts acknowledged purchases whose grant was refused (a Policy still loading, a full inventory); these now stay open.
- An item whose
OnUsewas still running could also be sold, crafted or traded away. - A ShopKit buy whose
Takeyielded and failed could leave the item behind. - Timed craft inputs were lost when the player left, teleported or the server closed before completion.
- A trade that could not settle one participant blocked both, and a blocked participant kept playing on a profile that could no longer save; they are now kicked so the next load recovers the trade.
MemoryDriver:updateretried after errors whose write may already have committed, applying a transform twice; it now retries only errors that prove the call never ran and reports the rest as “outcome unknown”.- A
"Memory"primary withBackupenabled wrote its backups to the production DataStore. It now defaults to a Memory backup, a backup store name over 50 characters errors at boot, and a driver on the live DataStore warns in Studio. - Templates: the Obby server no longer claims stages persist without a DataDriver.
- Docs said every DataStore write is a native version; versions are hourly (the first write in each UTC hour).
- A player rejoining within seconds could be kicked: on the same server while their leave save was failing (until it landed), on another server while the old one still had a stale connection for them (until it timed out), and after a server crash for 3 to 4 minutes (until the lock went stale).
- A live server whose saves stalled past the lock TTL could have its lock taken and its newer data rolled back; a holder that beats presence now keeps its lock.
- One slow or stuck save held up every other profile’s autosave, and with it their lock renewal.
- A leave save that committed but reported failure made a same-server rejoin lose its lock on the next save; the lock is now retaken when nobody has written since.
Compatibility
Section titled “Compatibility”- Channels with no validator now reject every payload even without a handler. A game that consumed an intent only through its
Intent.<Name>Bus topic needs a validator hook orOpen = true. - Wire change:
CKCUT_Playgained a Boolean8 has-origin argument. Deploy server and client together. - Wire change: unreliable state payloads gained a 2-byte sequence footer. Deploy server and client together.
- Defaults that changed: MeleeKit attacks require line of sight (
RequireLineOfSight = falserestores the old behavior); Impact ducksAmbient; Zones parts lose CanQuery and CanTouch; tagged instances outside Workspace no longer mount or get prompts; PartyKit follows platform parties (PlatformParties = falseturns it off); version-history fallback is on for DataStore profiles. - Signatures that changed:
Kernel.SessionEndgains an exit reason;LootKit:rollForand:awardreturn a refusal reason;Settings:setreturns whether it stored the value; TeleportRetry failure callbacks receivereason, result, message;SoftShutdown.attachreturns{ migrate, destroy }; AnimKitplayreturns nil while the rig waits for the Workspace. - Games that sell paid random items or allow trading of paid items must attach
Policyand mark those loot tablesPaid = true; without it,Paidtables roll for everyone after one warning. WeaponKit.serviceerrors unless everyMaxRangeis in (0, 15000], the Raycast length limit.- DataDriver
Codec = "Auto"profiles saved before this release decode with the legacy layout and are rewritten in the F64 layout by their next load (the load’s own write upgrades them). Do not downgrade the framework after that. Serde.inferreturnsNumberF64for integer samples. Code that relied on inferredNumberU32/NumberS32widths should pass{ LegacyIntegers = true }or write the schema explicitly.- Serde encodes of lists over 65,535 elements now error.
- A process body that waited on an engine primitive previously resumed early with nil; it now resumes when the engine call completes.
Preloadfailed batches count their ids as loaded-and-failed (progress reaches the total and the ids appear inFailed).- Replica uses new
CKRep2_*channels with U16 slots and two U32 counters, adding eight bytes per packet. Deploy server and client together; the reliable ordered transport requirement remains. Seedocs/event-driven-networking.md. - Wire change: deploy the server and client
Net/Packet(init.luauandTypes/init.luau) together. Old and new Packet builds cannot exchange server-to-client traffic; client-to-server framing is unchanged. - Request channels with a typed Response (for example
{ "NumberU8" }) must set aRejectValuethat encodes under it;defineRequestnow errors at boot otherwise.{ "Any" }responses accept a nilRejectValue. - Deploy the Settings server and SettingsClient together: rejections now arrive as
{ Accepted = false, Value }replies instead of a pushed sync plusfalse. Net.FloodDetectedconsumers receive fewer, coalesced reports. Count strikes with the newcountargument if you relied on one event per dropped payload.- Prediction options passed to a second
wrapof the same channel name are ignored (the first handle is returned), now across NetClients too. - Standard Roblox DataStores remain supported. Teleport timeouts bound retries, not engine completion; Roblox does not expose cancellation of an active teleport.
Matchmaking.enqueuecan returnfalse, "cancelled"when the player dequeued, matched or left while the call was yielding.- Leaderboards with the default backend return
falsefromsubmitfor fractional values. Scale them, for example to milliseconds. - LiveConfig documents carry a reserved
__CKWrittenAtfield;set("__CKWrittenAt", ...)errors. SoftShutdown.attach(kernel, options?): the new options are test injection points; existing one-argument calls are unchanged.- Under Server Authority,
Movementcorrections would fight the engine’s rollback. Run it in log-only mode withRubberbandStrikes = math.huge(strikes, the hook and the bus event still fire) or do not attach it. Rewind is unaffected. Rewind.validateShotreturnsNoShooterHistoryfor a shooter who died before the shot was validated.- Ragdoll
AutoDeathsetsBreakJointsOnDeath = falseon player humanoids. A manual on-deathenable()needs the same setting. - NPCs are server-simulated by default. Games that relied on client-simulated NPC physics must set
ClientPhysics = trueon the archetype. AClientPhysicsNPC stays server-owned after a ragdoll release. - Code that stepped a dead NPC through
npc:act()or read it fromKit:all()during the corpse window no longer sees it. UseNpc.Diedfor death reactions. - QuestKit’s default still counts an event when any argument is the session, victims included. Set
ActorArg = 1onCombat.Killobjectives to credit only the killer. - Custom ShopKit currency adapters must return true from
Takeon success; a falsy return now cancels the buy. - RoundKit schedules nothing before
start(). - SpellKit: code that lowers
session.Data.Manadirectly does not start regeneration until that session’s next completed cast. AudioKit:speak()with an unregistered name warns instead of throwing.- Game code that relied on AudioKit resetting
AmbientReverbevery 0.25 s must now set it. - VfxSuite spawns without
Lifetimeno longer stay forever. Give emitter spawns aLifetimethat outlives their particle bursts. AnimKit:attachRigcan yield on a client for up to 5 seconds when the model has no Animator yet. Client-created models should carry an Animator in their template.- Render-step names starting with
CKImpact_andCKCamera_are reserved (one per Impact and CameraKit instance; readRenderName). The internalStepperfields of Impact and CameraKit are gone; AudioKit and AnimKitLoopHandleis nil while idle. MoveKitcontextStatecan now be"Other". CustomWhenfunctions that treated anything but"Grounded"as airborne should check for"Airborne"explicitly.- DeviceBench governor windows need jitter to count as a breach: a perfectly even frame rate below the target is treated as a cap.
- HairKit fallback binding applies
bindCharacter(all accessories) after a failed rig, as it already did after a successful rig. - Code that relied on the client
NetTap.Activebeing true at boot (Studio orChloeKernelDebug) must open the NET window, or setNetTap.Activeitself. - Receipts grants that return
falsenow decline the purchase, and a kit refusal during a grant declines it unless the grant returnstrue. Grants that returnedfalsefor other reasons must return nothing ortrue. - A second
Receipts.attachon one MarketplaceService errors. Register every product on one processor. Validaterejecting a live record no longer restores an older version: the load fails closed. Relax the rule or add a migration before deploying a stricterValidate.- An explicit
Codecthat cannot encode and decode theDefaultsunchanged errors inDataDriver.new. DataDriver:restoreAtno longer writes the old version at load; save or release the returned profile to commit it.- ShopKit buys charge before granting. Custom
Currency.Giveadapters are now called to refund items that stopped fitting. - CraftingKit timed crafts write
profile.Data.Crafting(rename withField); a schema codec must hold that field or enable Extras. - While
session.ProfilePendingis true, timed crafts refuse withLoading, and achievement unlocks and quest completions wait for the profile. - Lock handoff uses one MessagingService subscription per server and one MemoryStore write every 3 s per server. Locks gain a
Presenceflag; servers on an older build are only taken over by lock age, so mixed-version deploys are safe.saveAllsaves profiles concurrently.
Released 2026-09-05.
- Add
Scopefor connections, timers, instances, cleanup callbacks and nested lifetimes.Kernel:createScope()owns scopes until early disposal or shutdown; the restricted kernel facade also exposescreateScope()andafter(). See Scope. - Add
ClientReplica.OnBatch(changes)for one notification per changed packet. All fields are committed before existing per-key callbacks; full resyncs preserve the replica and report removals. Seedocs/scopes.mdanddocs/event-driven-networking.mdfor contracts and examples.
- Reject invalid UTF-8 in persisted values, keys, and Settings input. Profile acquisition now uses unique fencing tokens and per-key load reservations; stale owners cannot overwrite a newer lease. Saves snapshot data before yielding, backup scheduling follows successful writes, and shutdown tracks unresolved releases.
- Replace sequential trade compensation with a durable DataStore-compatible journal and recovery before profile load. Replay IDs persist in metadata with the balance changes; canceled callers cannot retain dead save ownership. Receipts refuse codecs that discard their ledger, wait briefly for joining profiles, roll back throwing or yielding grants, and avoid duplicate notifications from overlapping receipt saves. See Transaction recovery.
- Limit incoming messages before decode/task allocation, reject duplicate pending request IDs, and validate RPC response payload/channel before removing the waiter. Typed arrays allocate only after element decoding and share an 8,192-entry message limit, configurable with Packet’s
MaxDecodedArrayElementsattribute. Harden public rewind input checks. Fuzzing validates by default; handler execution now requiresExecuteHandlers = true. See Packet and wire types. - Reject dead or replaced combat characters, use full 3D melee reach, interrupt spell channels on actual health loss, fix the movement client’s network method and server landing resets, and run equipped-item cleanup before removing its slot.
- Deliver governor-delayed replica fields on quiet ticks. Preserve newer leaderboard submissions while a flush yields and retain the best queued KeepBest value.
- Track late teleport failures with correlated, bounded retries and failed-player requeue. Unwind failed service boot and release kernel-owned sessions, listeners, hooks, processes, and singleton state on shutdown. See TeleportRetry.
Performance
Section titled “Performance”- Visit only active replicas and configured interest pollers when a shared cadence group wakes; quiet members no longer add traversal work. Preserve work queued during yielding sends and recover failed interest/delivery attempts.
- Share concurrent leaderboard reads per board/page limit and rotate capped write attempts across boards. Destruction releases waiting readers; late backend results cannot repopulate the cache.
- Skip network debug preview formatting when capture is inactive, paused or the channel is muted. Direct
NetTap.record()behavior remains compatible. - Use a cancellable deadline heap and one earliest-deadline wakeup instead of scanning recurring jobs every frame. Idle bound schedulers disconnect;
aftersupports one-shot work. Add opt-in recurring staggering and a production task-profiling switch. Yielding recurring callbacks cannot overlap themselves. - Sleep empty projectile, sweep, hazard, effect and zone jobs. Make replica delivery dirty-driven with optional manual interest; disconnect idle Packet pumps and replace channel/profile readiness polling with events and timeouts.
- Reconcile reusable Sword Factory changes: allocation-free spatial filters, bounded sparse queries, Pool forget, service-registration milestones, backup cadence, live configuration and settings reliability. Keep newer transport, persistence and lifecycle safeguards. See Milestones.
- Add an optional runtime-only model and conflict-aware vendor synchronization tool. Development tests and source remain available. See Releases and vendoring.
- Reuse player rosters and encoded snapshots/deltas during replica ticks; iterate spatial NPC candidates directly. See
docs/performance.mdfor remaining scaling gaps, storage costs, and profiling requirements.
Validation and compatibility
Section titled “Validation and compatibility”- Add portable isolated Luau regression runners and make tagged releases depend on the shared CI gates. Engine/service doubles do not replace Studio and published-place integration tests. See Regression runners.
- Trade mutators and receipt grants must not yield. Trades require one DataDriver instance, and all server profile writers must honor transaction ownership/blocked state. Uncertain trade outcomes require recovery rather than compensation. Replay ledgers remain bounded.
- HTTP backends must implement atomic ETag/If-Match updates and If-None-Match creation. Missing preconditions fail closed. Optional DELETE supports journal cleanup.
Changed
Section titled “Changed”- Tagged releases ship two models.
ChloeKernel-<tag>.rbxm(the development tree with TestKit, specs, and Simulation) andChloeKernelRuntime-<tag>.rbxm(runtime only, built fromruntime.project.json); the release job now runs the CI gates first. See Releases and vendoring. - Test place: diagnostics feed the debug panel in production.
Main.servercallsenableDiagnosticswhenever the place opted its debug surface on (ChloeKernelDebugtrue), not only in Studio, so the live test place’s SERVER window carries data; shipped games that leave the attribute unset pay nothing. The Studio-only boot gates (specs, audit, fuzz) are unchanged. - This entry was verified by CI only. Lint, whole-tree type analysis, Rojo build smoke, and the isolated Luau regression runners; no Studio play-test boot ran against this tree before tagging.
Released 2026-07-11.
- SpatialKit: spatial queries as sentences.
Kit:sphere(center, 15):players():alive():collect()replaces the per-call-site GetPartBoundsInRadius + character walk + team check: shapes (sphere, orientedbox(cframe, size), true-3Dcone(origin, direction, range, fovDegrees),capsule(from, to, radius)) chain population selectors (players(),npcs()= the NPCKit roster,tagged(tag); none = players plus npcs) and filters (alive(),team(name)via TeamKit so npc Factions count,hasTag,where(fn),exclude(caster)) into terminals (collect/first/count/nearest(n)sorted by distance). Positions read live at query time; a Spatial character index broad-phases the players population when given; every shape test is pure and exported. CombatKit-style abilities, AI scans, loot radii, and objectives all build on one query. See SpatialKit. - Curve: piecewise scaling curves.
Curve.new():add(1, 100):add(5, 350):add(20, 1200)holds sorted keys and answersat(x)(Linear/Smooth/Step ease per segment; Clamp or Linear extrapolation outside the keys) andsolve(y)(first-crossing linear inversion: “what level reaches 800 xp”); re-adding an x replaces its y,Curve.from(pairs)bulk-builds, keys binary-search. XP tables, damage falloff, prices, loot weights: one primitive instead of if-ladders. See Curve. - Stages: gameplay resource streaming. Zone-occupancy lifecycle (nothing to do with Roblox instance streaming): a stage binds resources to a Zones name, the FIRST player in runs
Load(returns the unload closure), the LAST player out unloads afterLinger(default 10s, so stepping out and back never thrashes spawns),OnEnter/OnLeavefire per occupant, andReplicashandles autobindZonewhile loaded. Occupancy rides busZone.Entered/Left(leavers count down automatically), a Zones instance at attach seeds players already standing inside at define time, and every closure failure warns without breaking the sweep. BusStage.Loaded/Unloaded/Entered/Left; bridge them for client cosmetics. See Stages.
Released 2026-07-11.
- Rng:
float(max)one-arg form.float(5)now means[0, 5); previously a lone argument defaulted max to 1, sofloat(5)degenerated toNextNumber(5, 1). Thefork()seed derivation comment no longer claims textbook FNV-1a (it is an FNV-flavored fold that truncates to stay inside Luau’s exact-integer double range; the contract is determinism per (seed, name), unchanged). See Rng. - AchievementKit: pre-profile progress merges on
Kernel.ProfileLoaded. Events landing before the profile attaches counted into session data and were then silently dropped; they now merge with QuestKit’s rule (unlocks carry over, the higher count per id wins, counts for already-unlocked achievements drop). See AchievementKit. - Hazards: destroyed anchors actually retire. A
Destroy()’d BasePart/Model keeps answeringPositionwith its last value, so the liveness check never fired; anchor resolution now treats a nilParentas dead and the next sweep retires the hazard (spec now exercises a real Instance as well as a duck double). See Hazards. - Hazards: serial wraparound skips still-live serials. A wrapped counter could reassign a live hazard’s serial, orphaning it without ever firing
Retired; allocation now skips live serials (same allocator stance as Projectiles) and returns nil only if all 65535 are in flight. See Hazards. - CameraKit: instant release restores the pose.
release()with zero/nil seconds restored CameraType and FOV but left the camera CFrame wherever the last shot pointed; it now lands back at the captured pose (Custom cameras re-solve from the character next frame regardless). See CameraKit. - Chronicle: double attach fails loud. Attaching a second recorder to the same kernel stacked wrappers (double-recording, with detach unwinding only the outer one); attach now errors until the existing recorder detaches. See Chronicle.
- Impact:
DuckBusdefaults to a real bus. The default was"World", which is not one of AudioKit’s default buses (Music/SFX/Ambient/Voice/UI), sopulse()with defaults errored on the first duck against a default-configured AudioKit; the default is now"SFX". The spring comment no longer claims “critically-damped-ish”; the live constants are genuinely underdamped (damping ratio near 0.5), matching the ringing the spec asserts. See Impact. - VfxSuite + CutsceneKit: registration caps at 255, not 257. The cap checked before incrementing, so a 256th registration succeeded with wire id 256 and only errored at play time (the id rides
NumberU8); both registries now fail at register with the corrected “at most 255” contract. See VfxSuite, CutsceneKit. - Gait: the root offset no longer compounds. The height/tilt offset composed onto
RootJoint.Transform’s live value, which is the kit’s own previous write whenever no animation drives the joint, accumulating drift over a session; the stepper now tracks its last written value (post-write readback) and strips it before composing, so the offset applies exactly once whether or not an Animator rewrites the slot. See Gait. - Gait:
PlantSmoothingactually smooths. The plant/unplantsetWeightcalls omitted the fade argument, snapping IK weights instantly; they now passPlantSmoothing, and the stepping path (plants, fades, root offset) gained spec coverage. See Gait. - Rules: the gate order is documented.
once -> cooldown -> countregardless of chain order;once()means a single fire per key ever, so combining it withcount/cooldownstill fires at most once (for repeated-but-throttled awards usecooldownalone). Behavior unchanged; the module header and cookbook now say so. See Rules. - CutsceneKit:
Linepublishes the raw string. The header no longer implies the kit calls Text; localization happens in yourCutscene.Linesubscriber (send keys, localize at render). See CutsceneKit.
Removed
Section titled “Removed”- HEAT debug window. The scheduler heatmap panel is gone; the CLIENT window’s hot-task list and budget bars remain the scheduler view, and the Panel’s profiler sampling reverts to its pre-HEAT form. The HOOKS window is unchanged. See Debug Panels.
Released 2026-07-11.
- MaterialKit: real-geometry surface dissolution. Where Dissolve flies voxel dots, MaterialKit erodes the actual rendered mesh: faces vanish and return in Perlin-noise rank order (optional directional
Bias), verified live: the baked MeshPart renders from the live EditableMesh object, so everyRemoveFace/AddTriangleshows immediately with no re-bake.bind(meshPart)opens the part’s own mesh asset (same two permission gates as HairKit, denials return(nil, reason));bindBox(part, {Subdivisions})needs NO asset permission: a generated subdivided box overlay hides the original (transparency restored on destroy) while collision stays on the game’s part.setProgress(alpha)erodes AND knits (a receding driver restructures the surface: removed triangles re-add against their recorded vertex ids),play({Duration, Reverse?})sweeps on one Heartbeat stepper,OnFaceGone(centroid, normal)per removal for dust hooks, pureMaterialKit.orderranks. Local-only VFX, same broadcast stance as Dissolve. See MaterialKit. - CameraKit: cinematic camera control as data. Shots declare what the camera does (
Static,Followwith exp-smoothed offset,Orbit,Rail= Catmull-Rom through CFrame points with eased progress and optionalLookAt), one PreRender stepper evaluates the active shot against LIVE targets each frame,blend(shot, seconds, ease?)eases from the real current pose so moving targets stay tracked through transitions,cutsnaps,release(seconds?)blends home and restores the CameraType; a dead target hands the camera back instead of freezing. Targets: Vector3, BasePart, Model, or function; per-shot FOV; pureCameraKit.ease/rail. See CameraKit. - CutsceneKit: scenes as timelines. Flat
{Time, Action, ...}event arrays shape-checked at register (Shot/Blend = CameraKit, Anim = AnimKit rigs resolved from a namedRigsroster so shared definitions never hold Instances, Sound = AudioKit at the scene origin, Vfx = VfxSuite, Line = busCutscene.Line(text, seconds)for Text-key subtitles, customHandlers); the server broadcasts ONECKCUT_Playpacket (U8 id + origin CFrame) and every client plays the whole scene locally;skip()jumps to the end: pending events drop, scene-started anims stop, the camera releases with a blend. BusCutscene.Started/Ended/Skipped/Line. See CutsceneKit. - DialogueKit: server-authoritative conversation trees. Nodes with Text-key lines,
Choiceswith per-playerWheregates (hidden choices cannot be picked; indices mean the VISIBLE list, and gates re-check at pick) and server-sideRunconsequences,Nextchains,OnEnternode actions; every reference validates at attach. Wire:DLG_Syncstate push per node ({} on close), fail-closedDLG_Choose/DLG_Advanceintents gated against the session’s live node;begin()stays server-side (wire it to an InteractionKit prompt), leavers clean up without intents. HookDialogue.CanBegin(fail-open); busDialogue.Started/Node/Choice/Ended(reason). See DialogueKit. - HOOKS debug window. Every hook point live in the F8 panel: fire and rejection counts, per-point chain cost (avg/max ms measured around the live call), fires/s sparkline, fail stance and handler count from
listPoints()(defined-but-never-fired gates show dimmed), pause/reset; decoratesHooks.fireand restores on detach. See Debug Panels. - HEAT debug window. Scheduler heatmap in the F8 panel: whole-scheduler step ms/s strip, color-graded per-task bars (green under 1 ms/s, amber under 5, red past it) over the hot-task profiler, per-task rolling history sparkline on click, deferred-task counter; the Panel feeds it samples so the two never fight over the shared profiler window. See Debug Panels.
- Rng: deterministic seeded randomness.
Rng.new(seed)wraps Roblox’sRandomas a first-class object:float(min?, max?),int(min, max)(inclusive),choice(list),shuffle(list)(Fisher-Yates over a copy, input untouched), andfork(name)deriving an independent child stream from (seed, name) via FNV-1a. Consuming an extra chest roll never shifts the NPC decisions; same seed, same sequence, every machine. See Rng. - Time: one clock surface.
Time.attach(kernel)setskernel.Time:now()(os.clock),server()(GetServerTimeNow, the claim-stamp clock),frame()(accumulated frame seconds, never pauses), andscaled()(game time honoringsetScaleandpause/resume: slow motion and pause become one switch instead of a hunt through every timer; the accumulator integrates scale×dt, never rewinds). Injectablestep(dt)spec seam; busTime.ScaleChanged/Paused/Resumed. See Time. - Profile snapshots.
profile:snapshot(label?) → id,:snapshots(),:rollback(id): in-memory point-in-time deep copies ofDatafor admin tooling and risky-mutation guards (take one before a trade, roll back on abort); the ring keeps the newest 8, snapshots survive repeated rollbacks, session-only and never persisted; persisting a rollback is the caller’ssave(). See Profile snapshots. - Rules: bus automation engine. Declarative event automation without spaghetti subscriptions:
Engine:on(topic)chains:where(predicate)(arg filter),:count(n)(every nth surviving match),:once(),:cooldown(seconds), and:key(fn)(counting identity, default the first event arg, the session in every kit’s convention), then:run(action)arms it and returns{disconnect, Stats = {Matched, Fired}}; counters hold weak keys so departed sessions drop, actions run throughtask.spawnso a throwing rule never breaks the bus. See Rules. - AchievementKit: persistent conditions over the Bus. An achievement is a topic, an optional
Wherefilter, and a targetCount; progress lives inprofile.Data(Field default"Achievements") so it survives rejoins, unlocks fire once per player ever,Rewardruns at unlock, andprogress(session, id)/unlocked(session, id)read out for UI; busAchievement.Progress(session, id, count, target)andAchievement.Unlocked(session, id). See AchievementKit. - Chronicle: kernel flight recorder.
Chronicle.attach(kernel)decorates the liveBus.publishandHooks.fire(restored ondetach()) and records every event and hook verdict into a ring:trace("Inventory")returns the last 30 seconds of entries whose topic or args mention it,trace(fn)filters by predicate,format()prints the timeline: “why did this player lose their sword?” becomes a lookup instead of a guess; summarization keeps names and numbers, never object references, so the recorder cannot pin sessions. See Chronicle. - Text: localization over locale tables.
Text.attach(kernel, {Locales, Default?})resolvesget(player, key)through the player’sLocaleId, its language half (fr-fr→fr), then the default; a key missing everywhere warns once and returns the key so untranslated UI reads as its key instead of erroring;format(player, key, vars)interpolates{Name}placeholders (unmatched stay literal), pureText.interpolatefor tests; send KEYS over the wire and localize at render. See Text. - Sweeps: phantom hitboxes for big rigs. Server-authoritative attack volumes authored as root-local capsule tracks (pure functions of swing phase), reconstructed in world from the live root CFrame each tick and tested mathematically against player capsules with sub-stepping between phases, so a one-tick tail snap cannot skip a target; per-swing victim dedupe, Spatial broad phase, zero dependence on physics parts or replication lag. Bus
Sweep.Hit(swing, victim, position); damage stays game-side. See Sweeps. - VfxSuite: declarative timed VFX pipeline. Multi-stage effects author as flat
{Time, Action, ...}event arrays shape-checked at register (Spawn = pooled template withLifetimeauto-release, Emit = particle bursts on named emitters, Sound = AudioKitplayAt, Dissolve = a Dissolve play on the spawn, anything else = customHandlers); the server sends ONECKVFX_Playpacket (U8 id + CFrame) and every client drives the entire lifecycle locally in one Heartbeat stepper; sequences register on both machines in one shared module. See VfxSuite. - Impact: screen-space feedback coordinator.
pulse(origin, options)localizes intensity by camera distance (quadratic falloff), kicks a damped camera spring (pulses stack), side-chain ducks an AudioKit bus with auto-release, and dips blur/color correction only when DeviceBench quality clearsPostQuality: low-end devices keep the shake and duck, never the post-processing cost; pureintensityAt/springStep. See Impact. - Gait: procedural foot planting and body attitude. Each leg down-casts from a look-ahead point (
effector + velocity * LookAhead) and drives an AnimKit Reach IK onto the hit (airborne legs fade back to the animation), the root joint Transform carries a smoothed body-height offset over uneven footing plus a velocity-derived bank/pitch tilt (the dragon leans into the pivot before the track shifts); all client cosmetic through Motor6D Transform slots and IK targets, one PreRender stepper for every bound gait; puretiltFor/heightOffset. See Gait.
Released 2026-07-10.
- Projectiles: authoritative curved paths. A per-definition
Path(seed, age, origin, velocity, flightSeconds?) → Vector3evaluated identically on both machines: the server sweeps each tick’sPath(t-dt) → Path(t)segment for walls and capsule hits (the hitbox follows the curve exactly, no more straight hit line under an arcing visual), clients render the same function and bank along its tangent. Deterministic from spawn-packet data (serial seed, origin, velocity, flight seconds): zero extra wire bytes; keep the function in one shared module for both defs.PathOffsetremains cosmetic-only for flourish that must not move hits. See Projectiles. - Hazards: dynamic procedural zones with a client mirror. Annulus bands (inner/outer radius + height) evaluated as distance checks in a cadenced sweep, no phantom parts: radii as functions of age (expanding fire rings), anchors as fixed points or moving Instances (the band follows the caster and retires if the anchor dies),
Durationauto-retire with everyone inside receiving Left before Retired, and occupancy tests each occupant’s last-position→position SEGMENT against the band so a sprint through a thin ring cannot tunnel between samples (a through-crossing fires Entered then Left in the same tick). Wire:CKHZ_Spawn(serial, defId, origin, seed, duration, anchor?)+CKHZ_Retire(serial);HazardClientrenders pooled, seed-deterministic ring VFX from the same shared radius curves, following moving anchors, safety TTL past duration. Damage stays game-side: apply an Effect onHazard.Entered, cleanse onHazard.Left. BusHazard.Spawned/Entered/Left/Retired. See Hazards. - ProjectileClient:
OnImpactcarries the wire serial (ported up from Withcraft&Wizardry).OnImpact(position, velocity, serial): the serial is the same seedPathOffsetreceives, so impact cosmetics reproduce the exact per-shot roll the visual flew with; extra arg, old callbacks ignore it. See Projectiles.
Changed
Section titled “Changed”- ProjectileClient: facing comes from authoritative velocity (ported up from Withcraft&Wizardry). Only curved rigs (
Path/PathOffset) bank along their rendered motion; everything else orients on the true heading, so the muzzle-blend convergence no longer rolls and pitches wide visuals (blade slashes) for the whole flight of a short-range shot. See Projectiles.
Released 2026-07-10.
- ProjectileClient: the cosmetic stack (ported up from Withcraft&Wizardry).
PathOffset(seed, age, velocity, flightSeconds?)per-frame curve hook,OnRender(visual, seed, age, flightSeconds?)per-frame visual hook,CosmeticOrigin(caster, origin)latency masking (the visual spawns at the muzzle THIS client currently sees and converges onto the true path, capped at 25 studs),OnImpactreceives the impact velocity as a second argument (orient VFX to the surface the shot died on), and shots with known flight seconds park at their landing point instead of overshooting while the hit/expire packet is in flight. See Projectiles. - Projectiles: per-shot flight control. The spawn packet carries flight seconds and the caster (cosmetic envelopes and latency masking read them),
fire()takes optionalmaxDistance(point-targeted expiry: lifetime caps atmaxDistance/speed) andspeedMultiplier(per-shot speed with no wire change),OnExpirereceives the expiry position and the expire packet carries it to clients. See Projectiles. - Movement:
AntiExploit.Exemptstanding exemption.kernel.Bus:publish("AntiExploit.Exempt", player, true|false)for server-sanctioned free movers (broom riders, vehicles, scripted knockback); unlike repeated Forgive it never resets the Rewind buffer, so lag compensation keeps validating the rider’s shots. See Movement Monitor. - Packet: paged name-to-id mappings + early-packet parking. Channel ids ride paged
Configurationchildren instead of RemoteEvent attributes (Roblox caps an instance’s total attribute payload at 1024 bytes; a real game’s channel count blew past it and errored channel definition), and payloads arriving before their id page park and replay when the map lands instead of erroring the receive loop. See Packet & Wire Types. - NET window: whole-game traffic row. “Game total” shows this machine’s full network in/out KB/s (engine replication + physics + every remote) from the Stats service next to the kernel-channel Wire row. See Debug Panels.
Changed
Section titled “Changed”- Diagnostics: per-role child containers. Kernel diagnostics write short attribute names on
ChloeKernelDiag<Role>Configuration children instead of ~29 long-prefixed attributes on ReplicatedStorage itself (the 1024-byte attribute cap errored the diag task in real games); hot tasks ride three 50-char-capped attributes; the debug panel translates legacy keys. - Debug panels boot hidden. The F8 panel and SIM control start with their ScreenGuis disabled; F8 toggles them in. See Debug Panels.
- NetTap: Instance sized at zero bytes. Instance refs ride the remote’s engine side array, not the kernel buffer; refusing to size them zeroed the wire counter for any Instance-bearing channel.
Released 2026-07-10.
- WizardDuel template: casts complete. All three spells now declare
ChannelSecondsand SpellKit defaults a missing value to 0 (instant) instead of erroring inbeginCast; WizardDuel and GunArena clients call the intent handle’sfire(...)(the lowercase dot-call NetClient actually returns) instead of a nil:Firemethod. See SpellKit, Starter Templates. - Rewind: the claimed fire direction now gates hits.
validateShotrejects claims whose origin-to-hit vector strays pastDirectionDotMin(default 0.94, about 20 degrees) fromShotClaim.Directionwith reasonDirectionMismatch; previously the field was carried but never read. See Rewind. - Studio boot gate: spec failures halt the play test.
Main.serverasserts zero failed specs from both suites, matching the audit and fuzz gates instead of warning past red tests. See TestKit. - LiveConfig: TTL expiry no longer splits the fleet. A poll that finds the document gone (MemoryStore TTL) reverts every override so long-lived servers agree with fresh ones booting into defaults; unreachable MemoryStore still holds current values. See LiveConfig.
- Analytics: playerless drops are counted and WatchErrors warns. The default
LogCustomEventdestination counts skipped playerless entries inStats.DroppedPlayerless, and attaching withWatchErrorsplus the default destination warns that every ScriptError event would be dropped (they are playerless by construction). See Analytics. - Settings: rejected client writes roll back. The kit’s validator pushes the authoritative value over
Settings_Syncon rejection (unknown key or invalid value), so SettingsClient’s optimistic cache self-corrects instead of keeping the rejected value. See Player Settings. - ShopKit: stock caps survive rejoins. Per-window buy counters live in the profile when one exists (rotating windows persist until they roll, static-shop caps become per-player lifetime), stale window counters prune on the next buy. See ShopKit.
- InventoryKit:
Intents = falseand a MaxSlots wire guard. A second attach on the same kernel can skip INV_* channel wiring instead of crashing on redefinition, andMaxSlotsasserts 1..255 at attach (slot indices rideNumberU8; larger bags would have client-unreachable slots). See InventoryKit. - CompanionKit: leavers never orphan companions. Session-leave cleanup moved out of the intent wiring, so
Intents = falsekits destroy a leaver’s companion too. See CompanionKit.
Changed
Section titled “Changed”- README truth pass. Removed the nonexistent
Npc.Idle/Npc.IdleEndedtopics,Npc.Tacticdocuments its three real roles, channel integer coercion documented as wrapping (replica fields clamp),Boolean1marked channel-only (replica schemas useBoolean8),BonePhysics.attach()in every example, receipt ledger default corrected to 2000,rebindexamples carry the device argument, Bad hearing blur corrected to 32 studs.
Released 2026-07-09.
- ActorPool: fork-join over the worker pool.
pool:forkJoin(items, {ChunkSize?, Context?})splits a batch into chunks (default 4 waves per actor) and every actor pulls the next chunk the moment it finishes its last, so uneven items self-balance across the pool; results stitch back in item order and the calling coroutine resumes once with(true, results)or(false, firstError). The worker fn runs asfn(item, context)per item; a chunk failure carries the absolute item index, stops new sends, and drains outstanding chunks. Compose phases by forking again over the joined results.dispatch()is unchanged. See ActorPool. - TablePool: scratch table recycling.
acquire()/release(t)recycle cleared Lua tables with their capacity intact (double release fails loud, idle stack caps at 256); Spatial grew allocation-freeradiusInto/boxInto/coneIntovariants that append into caller scratch, and the hot sweeps adopted them: NPCKit target-scan prefilters and Projectiles per-projectile candidate sets ride pooled scratch, NetGovernor per-player stats tables reuse in place. See TablePool. - Mounts: map-authored attachment points. Tag any instance
CKMount, set aMountTypeattribute plus per-type attributes, andMounts.attach(kernel, options)wires the systems at level load:Soundmounts become AudioKitplayAtemitters (SoundName,Volume?,Speed?),Zonemounts feedZones:addPart(ZoneName),BoneChainmounts bind BonePhysics chains (Damping?,Stiffness?,GravityScale?,WindScale?); built-ins mount only when their system is passed, so server and client attach off the same tagged map.Handlersextend and override with custom types returning cleanup closures; tag add/remove signals mount and unmount live (StreamingEnabled maps mount what streams in); handler failures warn and stay contained. BusMount.Added/Mount.Removed. See Mounts. - Spatial: flat spatial-hash index. Pointer-free grid over ids at world positions (packed integer cell keys, exact-filtered queries, coordinates clamp at ±524k studs, same-cell moves are position writes):
radius/box/cone(horizontal FOV dot) /nearest(expanding rings), plusSpatial.characters(kernel)for a self-refreshing player-root index. Wired consumers:Projectiles.attach { Spatial }replaces the per-tick full-roster scan with a per-projectile radius query (fresh roots for the selected candidates keep hit math exact),NPCKit.attach { Spatial }refreshes the index inupdate()and broad-phases target scans through a stale-padded radius before any sight test. See Spatial. - NetGovernor: adaptive per-client replication. Grades every player Good/Strained/Poor from ping median + jitter (mean absolute deviation over a rolling window), unreliable packet loss (sequence-numbered echo probes over
NG_Probe/NG_Ack, held until the client’sNG_Readyhandshake so join silence never reads as loss), and the DeviceBench quality hint (NG_Device, clamped, floors at Strained, never Poor by itself); the worst axis wins. ReplicaService sends deltas to Strained clients every 2nd tick and Poor every 4th with per-player phase stagger: skipped fields bank per subscriber and merge into the next on-cadence send, so discrete state never goes missing and snapshots/removals always send; unreliable state channels grew{ Priority = "Low"|"High" }and Low traffic sheds to Poor links before the wire (probe channel is High: shedding probes would lock the tier down). Shed counts inGovernor.Stats; busNet.TierChanged(player, tier, stats); client halfGovernorClient.attach(). See NetGovernor. - Forensics: anti-exploit flight recorder. Every player’s kinematics roll through a ring (
[t, position, look, velocity, humanoidState]atRecordHz10 forWindowSeconds20); a watched bus topic (defaultAntiExploit.MovementViolation) freezes the window into a capture and recordsTailSecondsmore; re-flags during the tail append reasons to the same capture and leavers finalize immediately. Delivery: function destination or{ Url }JSON POST (never blocks the sweep, failures fall into the in-memory ring, lastMaxCapturesreadable viacaptures());export()emits the JSON a web tool can rebuild the run from;Forensics.replay(capture)walks an interpolated ghost rig through the movement in Studio. Captures hold movement, reasons, and the user id only. BusForensics.Captured. See Forensics. - HairKit: automatic hair rigging over EditableMesh. Boneless catalog hair gains a generated skeleton at runtime:
plan()partitions mesh vertices into radial sectors x depth bands and emits a bone chain per sector (default 10 bones) with every vertex weighted to at mostMaxInfluencesbones (default 2, clamped to 4, weights normalized; root-plane vertices bind the root and never pull band centroids);rig()injects the skeleton (AddBonewithParentId/Virtual,SetVertexBones,SetVertexBoneWeights), registers the content into the DataModel (CreateDataModelContentAsync(Content.fromObject(mesh))), bakes throughCreateMeshPartAsync, swaps the hardware-skinned result into the MeshPart in place, and creates the Bone instance skeleton the mesh links to by name (write-once, read-many: the baked editable stays rooted for the part lifetime and is never mutated again). Sway rides BonePhysics (its single stepper, camera-distance culling, DeviceBenchMaxChainsbudget).attach()= client bulk manager (no per-accessory scripts);HairKit.server()rigs server-side so the baked bones replicate to every client once: zero per-frame network traffic in either mode. Two permission gates verified live: the “Allow Mesh & Image APIs” experience setting (supported()probes it with a realAddBonecall) and per-asset read permission (CreateEditableMeshAsyncloads experience-owned or creator-owned meshes only; third-party catalog hair returns(false, reason)); either failure keeps thebindAccessorypendulum fallback. BusHair.Rigged. See HairKit. - Dissolve: vertex-sampled voxelizer with noise-advected dissolve, restructure, and particle morphs. MeshParts sample EditableMesh vertex positions (surface-shell fallback with Ball/Cylinder silhouette filtering: interior cells drop so nothing dissolves as a filled box; permission denials cached per asset so avatar effects warn once per session), home positions ride the target’s anchor part so reassembly, restructure, and morph endpoints follow moving targets (a player who walks off mid-effect reforms where they are), points snap to a
DotSizevoxel grid with per-cell dedup, and dots advect through a 3D Perlin field (ScatterSpeed,NoiseScale,Drift) while fading;Reverseassembles instead;Duration = nilreturns a manual controller whosesetProgress(0..1)erodes the surface noise-thresholded per dot, and dots above the threshold ease back to their surface cell and resolidify (ReturnSpeed) so a receding driver restructures the target (proximity wards that knit shut).morph(source, destination)voxelizes source and flies every dot into destination’s sampled shape: bottom-up pairing (pairPoints, every destination cell used), staggered launches (Stagger), Perlin turbulence arcs (Turbulence), source-to-destination color lerp, and then reveals destination and keeps source hidden; avatars are first-class targets (Decals and Textures hide with their parts): disintegrations, character-to-statue morphs, reforming sources viaReverseplays; chained effects restore the pre-effect transparency, never a mid-chain hidden value.Chunks = true(play and morph) clones every source part as a full-detail flying chunk (meshes, textures, faces, and SurfaceAppearance survive, joints/welds/sounds/emitters strip) riding the same threshold, advection, or morph flight while shrinking into the dust, which keeps original avatar detail where denied-mesh grid sampling would box it; chunks destroy on finish. One Heartbeat stepper across every active effect, pooled dots positioned throughworkspace:BulkMoveTo,MaxPointsscaling 500..5000 by DeviceBench quality when unconfigured,CustomDottemplates,Spin. Local-only VFX; broadcast the trigger for shared playback. See Dissolve. - Starter templates. The repo’s
templates/directory ships three complete minimal games as reference code (GunArena: WeaponKit + Rewind + TeamKit + RoundKit + Leaderstats + global boards; WizardDuel: SpellKit + MeleeKit + MoveKit + curse/ward Effects; Obby: CheckpointKit + air steps + kill-brick Zones + ascending speedrun board). One server module and one client module each; linted in CI alongsidesrc. See Starter templates. - Studio boot gates in Main.server. Every Studio play test now asserts zero High-severity
securityAuditfindings after start and runsFuzz.runagainst every registered channel when the first session joins; handler errors fail the play test loudly. See Fuzz & audit. - StreamingEnabled stance documented. From a module audit: server-authoritative systems are unaffected; client-side rules for BonePhysics binds and long-lived model references. See StreamingEnabled.
Changed
Section titled “Changed”- API consistency: kernel-integrating modules use attach().
InputDriver.attach(kernel)andMoveKit.attach(kernel, options)are the canonical constructors;.newremains on both as a deprecated alias. Plain object constructors (Pool.new,NetClient.new) and service definitions (WeaponKit.service) keep their shapes: the rule is attach = wires into a kernel, new = plain object, service = definition forregisterService.
Released 2026-07-08.
- PartyKit: same-server player parties. Invites with TTL expiry (sweep-cleaned), one party per player (accepting leaves the current party), lazy creation (the party forms on the first accept with the inviter as leader), leader kick/promote, leader inheritance on leave (longest-standing member), disband at zero members, session-end cleanup of membership and pending invites both ways. Six fail-closed intents (
PT_Invite/Accept/Decline/Leave/Kick/Promote, UserIds as F64 on the wire),PT_Syncroster pushes andPT_Invitednotifications,Party.CanInvitehook (fail-open). Parties never touch the combat gates;samePartyandmembers(group-queue feed for Matchmaking) are the integration surface. BusParty.Created/Disbanded/Joined/Left/Kicked/Invited/Promoted. See PartyKit. - Effects is now a full status system.
TickSeconds+OnTickperiodic ticks (poison, regen) with catch-up bounded to 8 per sweep;Categorydispel groups withcleanse(session, category?)(no category = every categorized effect; uncategorized effects never cleanse);Immune = {categories}on an effect blocks covered applies while it holds (wards are effects;applyreturns 0 and publishesEffects.Blocked); owners receive aCKEffectsstate push after every change ({[name] = {Stacks, Remaining?}}) for status-icon UI. Bus addsEffects.Blocked(player, name, byEffect)andEffects.Cleansed(player, category?, count). See Effects. - LootKit: weighted loot tables with persisted pity. Entries yield items (
Countfixed or{min, max}), nested tables (depth capped at 8, references validated at attach), or nothing;PityAfter = Nforce-hits on the Nth consecutive miss with counters persisted per player in profileData.LootPity(rollFor/awardadvance pity, plainrollnever does);award(session, table)grants through InventoryKit and returns(granted, overflow). BusLoot.Rolled/Awarded. See LootKit. - CraftingKit: recipes over InventoryKit. Inputs check atomically before any take;
Seconds > 0queues one timed craft per session withcancel()refunds;Stationrecipes gate on the caller’s claimed station with proximity verified in the fail-openCraft.CanCrafthook; output that does not fit publishesCraft.Overflow; fail-closed intentsCR_Craft {recipeId, station}/CR_Cancel. BusCraft.Started/Completed/Cancelled/Overflow. See CraftingKit. - ShopKit: soft-currency shops over InventoryKit. Currency is an adapter (
{Get, Take, Give}; default reads a profile field,CurrencyField = "Coins"); rotating stock derives fromSeed + floor(clock / Every)so every server lists identical stock with zero coordination;Stock = Ncaps buys per player per rotation window; buys grant first and charge for what fit (partial grant = partial price, full inventory = no charge); fail-closed intentsSH_Buy/SH_Sellwith counts capped at 99. BusShop.Bought/Sold/Rejected. See ShopKit. - CompanionKit: owned NPC followers. Familiars/pets from NPCKit archetypes: one per session (re-summon replaces), a follow loop that walks at
FollowDistanceand pivots beside the owner pastTeleportDistance, death cleanup withCompanion.Died, andAuraholding an Effects buff on the owner while summoned; ownership gates in the fail-openCompanion.CanSummonhook; fail-closed intentsCP_Summon/CP_Dismiss. BusCompanion.Summoned/Dismissed/Died. See CompanionKit. - Fuzz: hostile-payload sweep over every registered channel. The runtime companion to
securityAudit(): per-schema-type hostile pools (empty/10KB strings, format specifiers, control bytes, 0, -1, type maxima, NaN, infinities, NaN vector components, nil/nested tables for Any) drive each intent and request through the real hook chain + handler pipeline; payloads that pass validation and crash the handler collect inReport.HandlerErrors(assert it empty as a boot gate); validator throws count as rejects; deterministic perSeed;Driverinjectable for specs. See Fuzz & audit.
Released 2026-07-08.
- InventoryKit: the item model. Definition registry + slotted per-player bags: stacking (grants top up piles before opening slots), equip slots with displacement and
OnEquip/OnUnequip,OnUseconsumables (return true to burn one; draining a stack auto-unequips),movedrag-and-drop (swap or merge, equip references follow the item). State persists through the session profile (Persist = falsefor session-only battle-royale bags). Grants/takes are server-API-only: no client message mints items; clients ride four fail-closed intents (INV_Move/Equip/Unequip/Use) validated against the server’s books, and the owner gets anINV_Syncstate push after every mutation so inventory UI is pure rendering. HooksInventory.CanEquip/CanUse(fail-open); BusInventory.Granted/Taken/Equipped/Unequipped/Used/Changed. See InventoryKit. - Leaderboards: global top-N on OrderedDataStores. Queued submits flush on a cadence (newest value per key per window = one write no matter how fast scores change),
KeepBestboards only overwrite improvements through atomicUpdateAsync(Ascending flips the comparison for speedruns), failed writes stay queued and retry next window, andtop()serves a cached page (oneGetSortedAsyncperCacheSecondsregardless of readers; failed refreshes keep serving the previous page). The backend is a first-class seam: Roblox OrderedDataStores by default, custom stores as equals: implement declarativesubmit(board, key, value, keepBest, ascending)(the keep-best rule arrives as data, so an external DB applies it atomically its own way) or DataStore-shapedset(board, key, updater), plussorted; contract asserted at attach.WritesPerFlush(default 30) caps each window so provider budgets (ODS limits, HTTP rate caps) survive submit spikes; overflow keeps its queue spot. NaN/inf submissions refuse; BusLeaderboard.Updated(name, entries). See Leaderboards. - TeamKit: teams wired into the combat gates. Capacity-weighted auto-balance on session start,
assign/teamOf/sameTeam/players, optional Roblox Teams mirroring for player-list colors, and map-authored spawn ownership (tag partsTeamSpawn+Teamattribute; characters pivot to an owned spawn with the anti-exploit pardoned). Friendly fire off by default, enforced where damage happens: vetoes registered onWeapon.CanDamage(new fail-open hook in WeaponKit’s damage path) andMelee.CanHit; npc handles resolve throughArchetype.Faction, so faction NPCs and same-named teams read as teammates. BusTeam.Assigned(player, team, previous?). See TeamKit. - WeaponKit:
Weapon.CanDamagehook point. Fail-open gate fired before a validated shot deals damage ({Session, Attacker, Victim, WeaponId, Weapon}); TeamKit plugs friendly-fire policy in here, games add safe zones and duel rules without forking the kit. See WeaponKit.
Changed
Section titled “Changed”- Repo-wide comment rewrite. Every comment states contracts, constraints, units, defaults, and data shapes; rhetorical framing removed; option comments normalized to
-- <fact>. Default <value>; ~680 comments across 118 files with the code proven byte-identical by a comment-stripping diff; three stale factual claims corrected against the code (Net pipeline order, ServiceManager cycle behavior, AnimKit IK helper class).
Released 2026-07-08.
- MeleeKit: server-authoritative hand-to-hand with parries. Attacks are windup / hit-frame / recovery timelines on the SERVER clock; combos cancel recovery along declared
ComboNextchains (Melee.Combocounts the string); blocking chips (BlockScale), guard-breaks shatter blocks (full damage, guard drops), and timed parries beat both: a parry press arms aParryWindow(+LatencySlackso wire travel doesn’t eat honest frames) that kills the incoming swing and staggers the attacker (StaggerSeconds, x1.5 for parried guard-breaks; the triangle: parry > guard-break > block > attack > parry). Attempts consumeParryCooldownhit or miss, so spam can’t blanket a swing. Hit frames sweep range + facing arc with a fail-openMelee.CanHithook for friendly-fire policy. Players ride auto-wired fail-closed intents (ML_Attack/ML_Block/ML_Parry; “attack while staggered” dies in validation); combatants are entity-agnostic (sessions, npc handles, models), and npc victims with no manual parry roll their own skill-scalednpc:defend(): difficulty-driven NPC parries for free. Bus:Melee.Hit/Blocked/GuardBroken/Parried/Staggered/Combo/State. See MeleeKit. - MoveKit: abilities bound to movement context. “jump pressed while airborne” as a first-class trigger: abilities are a trigger (
Input = "Jump"ridesUserInputService.JumpRequest, other strings ride InputDriverInput.<Action>bus events, orkit:trigger(name)) plus a context gate (Grounded/Airborne/Rising/Falling/custom predicate over{State, AirTime, Rising, UseIndex, ...}), a charge pool that refills on landing, a cooldown, and a takeoff debounce (MinAirTime). Recipes:doubleJump({Power})(fresh scaled jump mid-air) andairStep({Steps, Boost, OnStep})(anime footholds: kills the fall, hops off the invisible platform,OnStep(position, useIndex)draws the glow pad). Ownership guarantee intact: the CLIENT executes (it owns its character’s physics),MoveKit.servermeters through the fail-closedCKMoveintent with its OWN charge/cooldown books and its OWN ground probe (a client that lies about landing gets no fresh charges);Forgive = truepardons the next anti-exploit sample for horizontal bursts. Bus:Move.Triggered(client),Move.Used/Move.Rejected(server). See MoveKit. - ProjectileClient fairness rule: no invisible shots.
MaxVisualsnow caps only the FULL cosmetic visual; overflow spawns render as minimal pooled neon tracers instead of being skipped, and any shot whose flight path passes withinThreatRadius(default 15 studs) of the local character renders FULL regardless of the cap. An incoming round the target can’t see is a fairness bug, not an optimization.MaxTracers(default 300) bounds pathological spam (threatening shots bypass every cap). New pureProjectileClient.threatens(origin, velocity, position, radius)+ spec. See Projectiles. - DeviceBench: granular per-axis device benchmark, computed quality budgets, and a 60fps governor. Three time-boxed Luau workloads (compute, allocation churn, coroutine resumes) measure how fast the client actually runs, and each becomes a CONTINUOUS 0.25..4 axis multiplier against a mid-range reference, decimals fully allowed; the geometric mean is the overall
Quality. Budgets are FUNDED PER AXIS: the workload a device is good at pays for the systems that stress it: strong Churn buysParticleBudget/VfxDensity, strong Compute buysBoneChains/BoneDistance/ProjectileVisuals, strong Resume buysAudioChannels, so a device that benched 3.4x on churn but 1.2x on compute gets near-max particles while bone chains stay modest.profile()computes the budgets from a Result, any exact quality number (profile(1.35)), or a tier name;Low/Medium/High/Ultratiers remain coarse conveniences forpick(). Client modules consume the profile when unconfigured: BonePhysics cullMaxDistanceand NEWMaxChainschain budget (past it the chains nearest the camera win, the rest freeze at their pose), AudioKitMaxChannels, ProjectileClient NEWMaxVisuals(pooled-render cap; hits still land past it). Every default is an overridable option and every cap is purely cosmetic.scale(min, max)is log2-mapped over the band: every doubling of device quality buys the same slice of the knob. The GOVERNOR holds a stable frame target at runtime. 60fps first, beauty second: it watches p95 frame times and nudges the effective quality ×0.8 after ~3s of sustained misses, ×1.15 back after ~10s of clear headroom (granular nudges, not tier cliffs), capped at the benched quality (frame luck is not capability), with axis ratios preserved while dialing (a churn-strong device dialed down still favors its particles); changes arrive viaOnChange(quality, profile)andDevice.QualityChanged. Time-boxed means weak devices run fewer iterations in the same ~90ms instead of hitching longer; results cache, reportPlatform, optionally sample a medianFrameMs, publishDevice.Benchmarked, and auto-run on first helper use: pay the bench during loading (pairs with Preload: asset warming is network-bound, the bench uses the idle CPU). A client measurement: cosmetics only, never authority. Reference constants are provisional pending a live calibration pass. See DeviceBench. - NPCKit is now HELPERS, not an AI runtime. The behavior system is gone: no priority-list
{When, Step}runner, no kit-owned tick loop, no built-in behavior factories (wander/idle/chase/investigate/hide/cover/strafe/attack/suppress/flank/flee/tree all deleted). The kernel ships syscalls, not game rules, and chase/cover/flank policy IS game rules. What remains is a thin spawn/despawn lifecycle (model clone, health, died-cleanup, zones tracking) plus per-npc state, and YOUR loop composes the helpers:npc:updateTarget(acquisition/sight memory/graded detection),npc:canSee/npc:sweep(cone-gated eyes, pie-slice fans),npc:aimAt(the full skill model),npc:act(movesets),npc:defend,npc:moveTowards/npc:faceTowards,kit:findCover/kit:findPeekPoint(cover SCORING without the phase machine; Direction-attribute nodes, BackAway ground-giving, squad spread all kept),kit:all()(the roster your loop walks), andkit:update()(squad director + due melee windups, called from your tick). Two new standalone helper modules:Senses: hearing with levels of ATTUNEMENT (Keen/Sharp/Average/Dull/Obliviousor any custom{Range, BlurStuds}), anisotropic blur (direction resolves well, distance poorly), and aDistanceoverride so route-measured occlusion plugs in; plusinCone/canSeevision gates. NPCKit’s own ears and eyes delegate here, so kit npcs and hand-rolled agents perceive identically.Pathfinding.follower: per-agent re-pathing/waypoint-following as a handle your loop steps (follower:step(position, goal) -> nextPoint, {Path?, Jump, Stuck}): goal-drift/grid-version re-paths, failed-search backoff, stuck watchdog, jump hints;npc:moveTowardsis now a thin wrapper over it and applies the point via Humanoid/injected Move.BehaviorTreestays as the pure strategic helper: build one per npc, tick it from your loop with the kit clock. See NPCKit, Senses, Pathfinding. - NPCKit trained-combatant pass: graded detection, pain response, ammo discipline, honest gunfights. Sight can now charge instead of snapping:
DetectionSecondsonupdateTargetturns acquisition into an accumulator (fills faster up close and against fast movers, decays unseen; half charge publishesNpc.Suspiciousand points the hearing memory at the glimpse so your search logic walks over for a look, full charge acquires), and re-sighting a target after real occlusion re-imposes HALF the reaction time: the reappearance is a fresh stimulus, not a free instant re-engage. Getting hit registers:npc:notifyDamage(attacker?)(called automatically by kit Hitscan/Melee on npc victims, wired to Humanoid damage on spawned rigs, published asNpc.Damaged) suppresses aim for 1.5s (readState.SuppressedUntilto duck an exposed peek) and drops an unseen attacker’s position into the hearing memory: a back full of bullets now turns the body around. The aim model grew up: leading uses the TRUE intercept solve (|delta+v·t| = s·t) instead of distance/speed, error is correlated drift + near-Gaussian jitter (bursts walk readably instead of statistically bracketing the target), continuous time-on-target tightens the cone, and suppression blooms it. Movesets:Magazine/ReloadSecondsforce the reload pause players push into (Npc.Reload), meleeWindupSecondstelegraphs the swing clock-driven with a real dodge window (Npc.Windup; landing re-checks the arc), and ranged actions in a squad HOLD FIRE when a squadmate blocks the lane (FriendlyFire = trueopts out).npc:faceTowardstracks targets with the body while standing still; a stopped npc’s frozen vision cone would otherwise lose a target circling behind it. The squad director fields real fireteams: nearest holds Pressure, the rest alternate Flank (sides split so the pincer closes from BOTH directions,Squad.FlankSigns) and Suppress (base of fire), and the blackboard expires 30s-stale intel so dead men stop being suppressed and departed Players stop being retained. - Pathfinding: feet-vs-photons split, one-flood hearing, off-mesh links, unstickable movers. Grid line of sight split into two honest queries:
lineOfSight(walkability) now also gates CONSECUTIVE cells along the line byMaxStepHeight, so a Theta* shortcut between two in-band endpoints can no longer walk an agent off a sheer cliff a stepwise search would refuse, while newsightLine(vision, now backingNPCKit.hasLineOfSighton grids) lets eyes cross valleys and pits that block feet and only occludes on ground RISING past the band. NewPathfinding:distanceField({Grid, Origin, MaxDistance})Dijkstra-floods route distances from one origin:emitSoundPath occlusion now runs ONE search per sound instead of one full A* per listener.grid:addLink(fromWorld, toWorld, cost?)authors jump/vault edges (ledges, windows) that A*/Theta* traverse but never Theta*-shortcut across; humanoid movers jump for waypoints rising past step reach. Movement robustness: failed searches back off 1s instead of re-burning the full expansion budget every tick against an unreachable goal,Grid.Version(bumped byrefresh/refreshRegion) invalidates cached npc routes when the map moves under them, a stuck watchdog (no ground covered for a second while trying) forces a repath plus an unstick hop, andPathMethod = "Roblox"computes off-tick:ComputeAsyncyields could previously stall the entire npc population from one navmesh brain. Also: any-angle search uses the euclidean heuristic (octile overestimates euclidean segment costs: inadmissible), the ground raycast setsRespectCanCollideso foliage decor stops rasterizing as floor, and waypoint arrival radius scales with cell size. The default rasterizer stopped reading transient BODIES as geometry: a cell probed while a player or npc stood on it cached as blocked (or as walkable ground at head height) forever, including the asker’s OWN start cell; its clearance column now starts at step height instead of the floor, so a 1-stud lip or shin-high debris no longer dead-strips the ring of cells around it (a SpawnLocation’s edge used to island the whole spawn platform). Newgrid:nearestWalkable(position, maxCells?), the forgiving front door for user-driven queries: clamps off-grid points into bounds and snaps clicks on top of walls to the nearest standable cell, ring by ring, instead of failing onGoalOutsideGrid/GoalBlockedtechnicalities. The clearance check also grew a NARROW PHASE:GetPartBoundsInBoxmatches BOUNDING boxes, and a wedge ramp’s bounding box is a full-height slab that blocked every cell of its own slope. Non-block shapes (wedges, cylinders, meshes, unions) now confirm against real geometry with footprint-sampled down-rays, so ramps rasterize as the walkable slopes they are. See Pathfinding. - BehaviorTree: reactive preemption + honest clocks. New
ReactiveSelectorre-evaluates from the FIRST child every tick so a higher-priority branch coming alive preempts a Running lower one (whose resume memory resets); it is the right root for combat brains, where “enemy appeared” must interrupt “investigate” mid-plan; plainSelectorkeeps the resume-directly semantics.tick()threads an optional clock intoCooldownnodes (pass the NPCKit kit clock for determinism under spec, without the global_setClockswap), andreset()is the documented move on target change: a plan mid-flight against the old target is a bug against the new one. See BehaviorTree. - NPC determinism + correctness touch-ups.
defend()rolls reactions in name-sorted order (hash-order iteration made WHICH reaction answered nondeterministic under a fixed Seed), hitscan/sweep victims resolve through nested prop models to the HUMANOID-bearing ancestor, and npcs ignore their own noise, soSounds.Topicscan safely listNpc.Hitscanso NPC gunfire alerts OTHER npcs without the shooter investigating itself. - NPCKit tactical layer: squads, directors, and behavior trees. Archetypes take
Faction(faction-mates never target each other) andSquad(auto-join a shared blackboard): members report what their senses ACTUALLY produced (sight fixes exact, heard positions blurred by the listener’s skill) viasquad:report, readable throughsquad:lastKnown(target)/npc:lastIntel(target); the per-squad director (stepped bykit:update()) allocates roles per engaged target, publishingNpc.Tactic(npc, role)for client animation polish; your loop readssquad:role(npc)to decide who pins the last-known fix and who curves the arc. Cover is map-authorable: tag parts (SpotTag) with an optionalDirectionattribute so a node only counts when the threat is on its covered side, and squadmates already camping a node push its score down.npc:sweep(direction, arc?, rays?, range?)slices the pie: a horizontal raycast fan across the unseen angle before the body commits. The vision cone gate is a dot-vs-cos compare that runs before every sight raycast. NewKits/BehaviorTree:Selector/Sequencecomposites with realRunning-resume memory,Condition/Actionleaves,Invert/Succeed/Cooldowndecorators,reset(), injectable clock. Build one per npc so plans never share memory, tick it from your loop. - NPCKit combat brain: vision cones, sight memory, defense reactions, cover scoring. Sight is a cone, not a sphere: new
FieldOfViewDegreesdifficulty knob (Perfect 360 → Bad 100) gatesnpc:canSee(target, maxRange?, fov?)against the rig’s actual facing before any raycast (hearing stays omnidirectional, covering the rear);RequireSighttargeting tracks briefly-occluded targets forMemorySecondsthen hands the LAST SEEN position to the hearing memory, so your loop hunts the spot like a soldier instead of pathing omnisciently through walls.kit:findCover(withBackAwayfor fighters that give ground) breaks the target’s line of sight from grid cells or tagged nodes;kit:findPeekPointfinds the corner beside a spot with eyes on the threat. Defense: per-archetypeReactions(Againstattack types, per-reactionDifficulty, cooldown consumed on ATTEMPT) rollDefenseSkill: kitHitscan/Meleeactions roll the victim’s reactions automatically when it’s an npc,npc:defend(context)exposes the same gauntlet to game code, andNpc.Defense(npc, name, success, context)publishes deflects AND fumbles. Per-actionDifficultyoverrides resolve at define-time (Perfect defense on a Novice trigger finger); anisotropic hearing blur stretches error along the sound direction (direction resolves well, distance poorly). - Ragdoll v2: rig-agnostic, build-once, owner-client replication, smooth down, clean recovery. NEW-format character rigs (AnimationConstraint joints, zero Motor6Ds) ragdoll by cutting the animated joints and TUNING the rig’s own ball sockets for the ride (friction via
FrictionTorque(default 60) + limits, everything restored on release); frictionless free-spinning joints were the first “spazzing ragdoll”; classic rigs keep the Motor6D→limited-socket swap, now also friction-damped. The residual spazz had two more causes, both fixed: the owner client now LATCHES the Physics state (auto-getup disabled + a StateChanged re-force; ground impacts knocked the state machine into Landed/GettingUp, whose standing forces wrestle the sockets) and limbs ride an auto-registered SELF-BLIND collision group (CKRagdollLimbs: jointed-pair no-collides missed hand-vs-hip and arm-vs-leg contacts, whose depenetration impulses shook the pile). No automatic force at all: a socketed stack loses its balance the moment the joints cut and crumples on its own; explicitImpulsehit flings distribute per part, since with rigid joints cut every limb is its own assembly and impulses on the massless ghost root moved nothing. DefaultFrictionTorquerelaxed 60 → 10 (the high value was masking the state-machine and self-collision spazz, both now fixed structurally), and joint cones widened to anatomical ranges: shoulders 120, hips 90, elbows/knees 80 (tight cones read as a stiff mannequin; limits only stop the impossible). NewOptions.Limitsmerges per-joint{Upper, Twist}overrides over the built-in table, so the flop is tunable per game. The ROOT joint itself is never cut: the HumanoidRootPart is the camera subject, and cutting its joint orphaned a massless, non-colliding ghost that fell through the world; the camera dove after it, and release “recovered” the character under the map; left attached, the root rides the torso and the camera lies down with the body. Recovery uprights the root +2 studs (it lies at torso height; standing needs leg room or GettingUp unfolds into the floor). Components build ONCE per model and toggle on re-ragdolls (no instance churn on the wire). Player characters: humanoid state is client-authoritative, so the server pushes theCKRagdollstate channel andChloeKernel/Net/RagdollClient.listen(netClient)answers on the owning machine; release WAITS for the server’s joint re-enables to replicate (standing up while joints were still cut was the broken recovery), kills linear AND angular momentum, stands the root upright with yaw kept, re-enables Animate fresh, and gets up; server-simulated rigs get the same momentum-kill + upright on their release. PlatformStand applies only to server-simulated rigs; stacking it on a player character made the two controllers fight. Verified live: state holds Physics the whole ride, torso angular velocity settles to zero within a second of the kick, recovery stands upright (UpVector.Y = 1.00) straight into Running. See Ragdoll. - Pathfinding: partial grid refresh.
grid:refreshRegion(minWorld, maxWorld)drops only the cells a moving wall or opening gate swept; the rest of the map keeps its raster, so continuously-moving courses re-path cheaply (the fullrefresh()stays for map-wide changes). - BonePhysics: boneless accessory sway + ambient wind.
bindAccessory(run automatically bybindCharacter) makes accessories WITHOUT bones move: the accessory becomes a swing-clamped pendulum hinged at its joint, steered through whichever joint kind wears it: Weld/Motor6D handle-side offsets, RigidConstraint handle-side Attachment CFrames (the modern catalog-hair case; nothing swapped or disabled), or WeldConstraints swapped for an equivalent local Weld and restored exactly on release.MaxSwingDegreeskeeps hair drifting instead of flipping. NewAmbientWind = Vector3sim option: a procedural breeze gusted by layered sines, no Wind function needed. Verified live on a real avatar: 10/10 accessories bound, hair swaying through its untouched RigidConstraint. See BonePhysics. - NPCKit: opt-in NPC-vs-NPC.
Archetype.Targetable = truejoins the default target roster so other npcs hunt that kind through ordinary acquisition (self-targeting excluded, faction-mates filtered); overrideGetTargetsfor full roster control. - AudioKit: the kernel’s all-in-one audio engine: bank-registered 2D/3D playback over auto-created bus SoundGroups (Music/SFX/Ambient/Voice/UI), with every fade, crossfade, and duck driven by ONE scheduler stepper (no TweenService churn, no per-sound Heartbeat connections, deterministic under spec). Sound instances and 3D emitter parts pool; channel pressure evicts lowest-priority-first (music is never evicted); ducks stack with deepest-wins semantics. Adaptive music mixes layered stems by WEIGHT on one locked timeline (
playLayers+setWeights/resync); cue points fire loop-awareAudio.Cuebus events;speak()queues dialogue on the Voice bus, auto-ducks, and publishesAudio.Subtitle/SubtitleEndedfor the game’s UI; reverb zones apply by listener position with priority overlap; soundscapes scatter randomized chirps over an ambient bed. Kernel-only integrations:Alertrings NPCKit ears so player-audible sounds are also NPC intel,Occlusion = "Path"muffles by the pathfinding route around geometry (walls block, corridors carry),bindSettingspersists bus volumes through the validated Settings service, andAudioKit.server/audio:listenbroadcast plays over one state channel. See AudioKit. - AnimKit: the animation counterpart: bank-registered clips played through per-rig controllers with track caching, crossfading exclusive
Groups (walk -> run without the T-pose blink), marker-to-bus forwarding (Anim.Marker), and an inverse-kinematics layer:rig:ik()wraps IKControl (LookAt/Reach/Transform/Rotation) with stepper-faded weights, Vector3 targets ride a movable helper,Propertiespasses raw overrides, andrelease()hands the skeleton back. Rigs die with their models;assetIds()feeds the preloader. See AnimKit. - Preload: the framework’s asset pool: collects every registered asset (
AudioKit/AnimKitbanks viaassetIds(), plus extra ids and whole Instances), dedupes, and warms them in batchedPreloadAsynccalls off-thread, narratingPreload.Started/Progress/Doneon the bus for loading screens;await()blocks a boot flow until warm; failures collect and report, never abort. See Preload. - BonePhysics: a lean, leak-free SmartBone replacement (
ChloeKernel/BonePhysics, client-oriented shared module): verlet chains over Bone hierarchies with a FIXED-timestep solver (hitch-clamped substeps; no frame-rate-dependent droop), flat arrays instead of per-bone objects, camera-distance culling that snaps back to the pose on wake, and a teleport guard so respawns don’t whip chains across the map. Poses write throughBone.Transform(the animation slot) composed against the parent’s effective world, neverWorldCFrame, whose setter permanently mutates the bone’s rest CFrame (verified empirically; that mutation is exactly the “SmartBone broke my skeleton” class of bug). Lifecycle is airtight: roots auto-unbind onDestroying,handle:destroy()restores Transforms,sim:destroy()releases everything.bindCharacterscans accessories/layered clothing for boned parts;bindPartsdrives plain part chains (stud tails) with the same solver. Settings per bind:Damping/Stiffness/GravityScale/WindScale; sim options include injectableWindandShouldSimulate. - Ragdoll: Motor6D-to-BallSocket ragdolls with per-joint swing/twist limits (R6 + R15 names), collision flips (limbs drape, root goes ghost + massless), optional death wiring,
Impulseflings,Durationauto-release, and aMaxActivecap that releases the oldest pile-up.release()restores EVERYTHING it touched: motors, collision flags, collision groups,AutoRotate,RequiresNeck, humanoid state. Replication is near-perfect at zero extra cost: the joint swap is server-side and the engine streams the physics; a player’s own character keeps its network ownership (per the kernel’s no-ownership guarantee), so their ragdoll simulates locally at full rate. Bus:Ragdoll.Started/Ended(model). - NPCKit combat model: hitscan, arcs, live difficulty.
Kind = "Hitscan"actions resolve through an injectable raycast with aim error only (no lead, it’s instant) and publishNpc.Hitscan(npc, origin, hitPosition, victim?)tracers;Action.Gravityruns a ballistic low-arc solve for lobbed projectiles (out-of-range lobs 45 degrees);npc:setDifficulty(nameOrTable)swaps the skill model mid-fight. - NPCKit routing skill + path events. New
PathSkilldifficulty knob (Perfect 1.0 → Bad 0.2): high skill takes any-angle Theta* and re-paths eagerly as goals drift, low skill runs plain A* and clings to stale paths; explicit kitPathMethodstill overrides. Every computed route publishesNpc.Path(npc, waypoints)so visualization/debug tooling never monkeypatches the finder. - NPCKit senses: hearing.
emitSound(position, {Range?, Loudness?, Source?})alerts NPCs with range AND accuracy scaled by difficulty (newHearingMultiplier/HearingBlurStudsknobs on every preset:Perfectpinpoints the exact position,Badhears a vague 30-stud blur; the ear model itself lives in the standaloneSensesmodule),Sounds.Topicsauto-emits from listed bus topics at the acting player’s position ({["Weapon.Fired"] = 60}= gunshots make noise with zero wiring), andSounds.Occlusionpicks the geometry model:"Through"(walls ignored, default),"Blocked"(walls stop sound, the “through-wall stuff off” switch), or"Path"(distance measured AROUND obstacles on the grid, maze-correct; no route = not heard). The heard fix lands inState.HeardPosition/HeardAt/HeardSourcefor your loop’s investigate logic; a fresh sound resets the search scratch fields. Bus:Npc.Heard(npc, heardPosition, source?). - Pathfinding: terrain- and height-aware grids. The default rasterizer now raycasts for ground (Terrain, parts, and model geometry all count) and requires the agent column above it clear; cells carry their ground Y, so waypoints follow elevation, adjacent rises past
MaxStepHeight(default 4) read as unclimbable cliffs in A*/Theta* expansion, and grid line of sight refuses to cross walkable high ground: a wall top still occludes even though you could stand on it. InjectedIsWalkablemay return a second ground-Y value to opt in; plain boolean rasterizers behave exactly as before. - NPCKit: server-side npc handles: archetypes carry a skill model with seven difficulty presets (
Perfectaimbot throughBad, or any custom{AimErrorDegrees, ReactionSeconds, LeadSkill, CooldownMultiplier}table, overridable per spawn) driving aim spray, reaction delay, moving-target lead, and attack pace, and movesets UNIFIED with player systems:Projectileactions fire the same kernel Projectiles definitions players shoot (npc handle rides as the owner session),Customactions receive a pre-aimed direction for pointing at existing WeaponKit/SpellKit server logic,Meleegates on range for creature attacks. Your game’s loop drives the handles (kit:all(),npc:updateTarget,npc:act,npc:moveTowardsre-pathing through Pathfinding as goals move); spawned models auto-track in Zones (Zone.EntityEntered/Left). Bus:Npc.Spawned/Died/Action. - Pathfinding: one handler, four methods, each lazily required on first use:
AStar(8-connected grid, octile heuristic, corner-cut safe),ThetaStar(any-angle: parents shortcut through line of sight for straight segments),Direct(single LoS test; grid raster or world raycast),Roblox(PathfindingService navmesh; yields). Grids rasterize a world rectangle lazily with caching (refresh()after map changes), take injectedIsWalkablefor RTS maps/dungeons/deterministic specs, and searches carry aMaxExpansionsbudget with reasoned failures (GoalBlocked,NoPath,BudgetExhausted). - QuestKit: quests as data: objectives count server-published bus topics (
{Topic, Count?, Match?, Filter?}), so there is no client input to validate. Default rule counts events whose args include the session/player;Matchpins an arg (zone name, weapon id);Filtertakes over for actor-less topics.AutoAssign,Repeatable(resets fresh on completion),OnCompleterewards, progress persisted via profile with transient/persisted merge on load. Bus:Quest.Progress(player, questId, index, count, required),Quest.Completed(player, questId). See QuestKit. - InteractionKit: exploit-proof ProximityPrompts: prompts are created from CollectionService tags (and follow tag changes live), but a trigger is client input, so every one re-validates distance (latency slack), server-side line of sight, and per-player cooldown through the fail-closed
Intent.Interactchain (kit gate at 50; game rules prepend) beforeOnInteractruns. Bus:Interact.Triggered(session, id, instance),Interact.Rejected(player, id). See InteractionKit. - Settings: persisted player preferences behind an allowlisted schema: client writes ride a rate-limited intent through the fail-closed chain where unknown keys reject, numbers clamp, strings cap, and string arrays REBUILD clean so hidden hash keys never reach storage; values persist via the DataDriver profile (player-chosen transient values win the merge),
Settings_Syncpushes authoritative values back, andSettingsClientgives the client fetch/optimistic-set/onChanged over the same channels (wire to InputDriverrebindfor persistent remaps). Bus:Settings.Changed(session, key, value). See Player settings. - Analytics: batched, sampled, rate-capped event pipeline:
track()is a queue insert, a background flush hands batches toAnalyticsService:LogCustomEvent(funnels/retention with zero infrastructure) or an injected destination (webhook/warehouse); per-event sampling and a per-minute cap protect the custom-event budget with every drop counted, never silent; optionalWatchErrorsbridgesKernel.ScriptError. Crashing destinations lose only their own batch. See Analytics. Kernel:securityAudit(): one pre-ship sweep over the whole security model: flags handler-bearing channels with no validator (High), gate hook points flippedFailOpenwhere a crashing validator would PASS payloads (High),Open = trueescape hatches to re-confirm against hostile input (Medium), and channels still on the default rate limit (Info); prints a severity-sorted summary and returns findings for CI-styleassert(#findings == 0)gates.HookRegistrygainedlistPoints(). See Fuzz & audit.- Zone-scoped replica interest:
replica:bindZone(zones, zoneName). Subscription flips the instantZone.Entered/Leftfires (full snapshot on entry, remove on exit, quantize baselines flushed) instead of waiting for the replica’s interest scan, which stays wired underneath viaisInsideso pre-bind occupants converge. Players outside a region pay zero bytes for its replicas. See Replica. - Boot readout floors at 0.01ms; a sub-hundredth-millisecond boot no longer prints a suspicious “0ms”.
- Zones: entity tracking, per-zone signals, multi-part volumes, tag wiring. Zones now detect NPCs/props/vehicles alongside players:
track(entity)/untrack(returns an untrack fn) andtrackTag(tag)(CollectionService, live; untag or Destroy fires the leaves) put entities in the sweep, riding newZone.EntityEntered/EntityLeftbus topics whileZone.Entered/Leftstay players-only.add()now returns a per-zone handle carryingEntered/LeftSignals plusOnEnter/OnLeavecallback options, so handlers stop name-filtering the global topics; session-end andremove()leaves flow through the same dispatch (handles fire everywhere). One zone can span several parts (add(name, { PartA, PartB }),addPart), andaddTagged(tag)builds zones straight from Studio tags: zone name from theZoneNameattribute (fallback: part Name), same-named parts unioning into one zone. New reads:entitiesIn(name),isInside(name, occupant). InjectedQuerydoubles and the default overlap-query reuse are unchanged. See Zones. - Built-in hook point & bus topic catalog. A new API-reference section documenting every hook point the framework fires (context fields, fail-open vs fail-closed vs observational, when it runs) and every bus topic it publishes (args, publisher), plus the consumed
AntiExploit.Forgivepardon topic and the hooks-vs-bus mental model (hooks ask permission, topics announce facts). Zones cookbook and API rows rewritten for the new surface. On this site: Hooks and Bus topics. - ProceduralKit: server-authoritative runtime geometry on Roblox ProceduralModels (Studio beta): archetype registry with fail-closed parameter validation (unknown names/wrong types reject, numbers clamp to Min/Max, OneOf enforced), per-model regeneration rate limiting (writes inside the cooldown buffer and flush as ONE batched rebuild: a spammed customization intent costs one regen per window, not one per message), session-bound lifecycle (
Owneroption),waitForGenerationsurfacingGenerationError, and a publicvalidate()for wiring client intents through the kit’s rules. Generators stay server-side by design: the peer that changes a parameter generates and the results replicate, so ServerScriptService generators are never decompilable. Ships an ExampleGenerator demonstratingparams:Pause()timeout safety and Seed-derived determinism. Bus:Procedural.Spawned/Updated/Rejected/Removed. See ProceduralKit. - Engine diagnostics that survive production: new
Diag{Role}ReplKbpsIn/Out,PhysKbpsIn/Out,Primitives,MovingPrimitives,Contactsattributes plus a panel Engine section (replication bandwidth, physics replication, awake primitives, contacts), none gated by memory tracking, so live servers keep a full engine debugging surface. Investigated enabling engine memory tracking by default: impossible. Roblox turns it off in all production builds before any code runs andStats.MemoryTrackingEnabledis READ-ONLY with no opt-in (staff-confirmed); ALL memory getters (incl.GetTotalMemoryUsageMb) return 0 + warn when off. The panel now states this plainly where the memory bars would be instead of silently omitting rows. - Frame-aware scheduler budgeting (server default): each step budgets the slack left under a 16.5ms frame target after everything else’s measured share of the frame:
Stats.HeartbeatTimeMs(the engine’s smoothed heartbeat-phase compute, idle excluded, verified empirically) plusStats.PhysicsStepTimeMs, with the kernel’s own smoothed share subtracted back out so its own work never shrinks its own budget. An idle server drains ~16ms of backlog per frame; an engine-heavy one backs off automatically, never below the 1.5msBudgetSecondsfloor; a frame-interval guard drops to the floor when frames run >25% late (covers unmeasured costs like replication serialization, and misreporting platforms). ExplicitBudgetSecondskeeps the old fixed-slice behavior;TargetFrameSecondsopts any scheduler in; clients default to fixed 1.5ms (the render thread is invisible to the frame stat); phase schedulers stay fixed so a second frame-aware scheduler cannot double-claim the same slack. See Scheduler. - Frame composition diagnostics, the frame decomposed by WHO is spending it:
Scheduler.Statsrecords the live budget, heartbeat-phase busy time, and physics time every step, plus a windowed step avg/max/deferred drained bytakeStepWindow(); newDiag{Role}FrameBusyMs/PhysicsMs/FrameTargetMs/StepMaxMsattributes; both panel windows grew a Frame section splitting measured compute into engine + other scripts / physics / kernel share (client adds render CPU) with a frame-compute bar against the target and per-row diagnosis tooltips. Overloadsimulation mode: ~20ms of scheduled burn demand per frame (8 tasks every step on a Low/Background mix) plus 500 entities: deliberately exceeds the whole frame target so the budget pegs,Deferredgoes nonzero, queues back up, and the red panel states show (verified live: 74-143 deferrals/window, frame compute ~18ms). HighIntensity reframed as the realistic load a modern server clears green in ~1.2ms/frame; that being visible is the point. See Simulation.- Simulation harness (
Simulation.server.luau+ChloeKernelServer/Simulation): three load modes selected by theChloeKernelSimulationReplicatedStorage attribute:HighIntensity(shooter-scale stress: 200 entity processes, hook/bus floods, replica delta churn, CPU burn),SmallGame(light baseline),Showcase(narrated 6-act feature tour incl. real haste/slow on present players and an anti-dupe replay demo). Hot-swappable mid-game, full teardown, Studio-gated, fake packet factories so simulated traffic never touches the real wire. See Simulation. - GcWatch: GC statistics from
gcinfo()sampling: allocation rate (the lever that drives Luau’s incremental GC cost; Roblox exposes no pause timings), GC cycle cadence, and reclaim sizes that tell leaks (heap climbs, reclaims ~0) apart from churn (heap stable, busy cycles). Auto-attached byenableDiagnostics(newDiag{Role}GcAllocKbS/GcCyclesMin/GcReclaimKbattributes), graded in both debug-panel windows, and reported in the simulation heartbeat. See GcWatch. - SimPanel + SimLoad (
Debug/): a SIM CONTROL window that switches server simulation modes live (Studio-onlyChloeKernelSimControlremote, mode-whitelisted) and runs client-side loads on the local machine; SimLoad is the role-agnostic rig set built on shared primitives only (processes, hooks, bus, serde, pools, burn) with its own GC-reporting heartbeat. Wired through the test place’s client Bootstrap; delete that block for a real game. See Debug panels. - DataDriver backups (
Config.Backup): rotated periodic copies to any second backend (DataStore{Name}_Backupsby default; Memory/Http/custom for external DBs), optional per-saveMirrorto a live slot, and load-time fallback that restores the newest readable backup when a record is corrupt or unmigratable (profile.RestoredFromBackup+Kernel.ProfileRestoredbus event). Exactly-once per key per interval game-wide with zero coordination. Backups ride the session-lock owner’s save path and the schedule lives inside the record, so no server count can duplicate them and server hops don’t reset the clock. Restore never bypasses session locks (corrupt ≠ unlocked) and never runs while the backend is down. New APIs:backupNow,listBackups,peekBackup(read-only, lock-free);Config.Clockinjectable for tests. See Backups. - SOLID untyped-value support:
"Any"is a first-class Serde schema type (adaptive best-fit per value, clear errors for unstorable kinds);Serde.schema(spec, { Extras = true })preserves fields absent from the schema adaptively instead of silently dropping them;Serde.infer(sample)builds a typed spec from plain data with safe wide widths (integers → U32/S32, floats → F64, never narrowed from a sample’s magnitude); DataDriver acceptsCodec = "Auto"(inferred from Defaults, extras on). See Serde. - Debug panel NET window + NetTap: a live wire inspector for kernel channel traffic from both machines: direction, channel, decoded args, payload bytes computed from schema widths, server processing ms / request round-trip ms, and verdicts (OK/Rejected/RateLimited/NoSession/Dropped incl. Packet flood drops and oversized unreliable sends). Capture is pausable (freeze the tail), clearable, and filterable (replica noise toggle); clicking a row pins it in an Inspector that shows the message both ways: decoded argument values AND the serialized payload as hex, generated through the same Packet type engine that writes the wire. Server entries reach the client on a debug-only mirror channel that never exists in production. SimPanel’s control path is now a real kernel intent, so mode clicks demonstrate the full pipeline in the window. ImGui gained a
selectablerow widget. See Debug panels. - Panel limit bars (consumed vs permitted): both scheduler sections show a frame-budget bar (
used / budget ms (%); 100% is where tasks start deferring; budget published via newDiag{Role}BudgetMsattribute) and the server memory row reads against Roblox’s 6.25GB process cap; ImGui bars now support hover hints. - Replica
Quantizedeadbands ({[field] = threshold}): writes that moved less than the threshold from the last replicated value update server data but skip the wire entirely; drift is bounded by the threshold, so slow movement still replicates once it accumulates. Sub-visible jitter on chatty fields (positions, progress bars) now costs zero bytes. The NET window’s replica rows also show per-recipient size (21B each x30) so fan-out totals don’t read as one large buffer. - Packet ported into the kernel (
ChloeKernel/Net/Packet): no more external package; the wire transport is now kernel-maintained, formatted to kernel standards, and bug-fixable in place. Attribution to Suphi Kaner (5uphi) retained in CREDITS.md; thePackages/folder is gone and all requires point inside the kernel. See Packet & wire types. - Serde fast engine (techniques from light/holy by @hardlyardi, MIT): schema and struct codecs now compile per-type writers that resolve byte widths at compile time, allocate once at the exact size, and run with zero capacity checks and no global-cursor swap: fixed-schema encode 463ns to 285ns (38% faster), decode 514ns to 367ns, dynamic schemas 22-27% faster, replication deltas at 270/293ns. Output is byte-identical to the wire-engine path (spec-asserted with cross-decoding); schemas using uncovered types (Any, CFrames, sequences, Extras) fall back automatically, so no stored record or wire format changes.
NumberVlqwire type: variable-length unsigned integer (7-bit continuation, up to 2^53): 1 byte under 128, 2 under 16384; usable in schemas, structs, and channels; clamps like the other integer types.- selene std extension (
buffer_bitops.yml) teaching selene 0.28 aboutbuffer.readbits/writebitsinstead of file-level allowances. - BusBridge fulfills the Bus’s advertised NetBridge:
ServerTopics(wildcards) auto-forward to every client as ordinary bus publishes,Bus:publishRemoteforwards any topic explicitly, and client publishes ride a rate-limited intent through exact-name whitelisting (fail-closed, no wildcards upward) plus theIntent.BusPublishhook chain, arriving as(topic, session, ...). See BusBridge. - MessageDriver: cross-server events over MessagingService: Serde-packed + base64 payloads, a clear error at the ~1KB ceiling, publish retries with backoff, per-message crash isolation for subscribers. Completes the trio: MemoryDriver = state, DataDriver = storage, MessageDriver = events. See MessageDriver.
- Net Registry: single-source channel definitions consumed by both sides (
Registry.define/server/client): schemas can never drift; declarativeValidaterules (Range/OneOf/MaxLength) compile into the fail-closed hook chains at priority 5;Net:onRequestadded for late handler attachment. See Registry. - Server log tail in the panel: server Logger lines mirror to the SERVER window on a debug-only channel (same gate/pattern as the NET mirror), giving both windows a live tail.
- SimLoad real-wire flood: client load modes fire the SimNoise kernel intent at rate (HighIntensity deliberately exceeds its 50/s limit so RateLimited verdicts demonstrate live in the NET window); NET byte estimates now cover
NumberVlq. Kernel:shutdown(): full teardown (diagnostics, GcWatch, services, phase schedulers, the main scheduler, and the bus) and clears the boot singleton, so a freshboot()on the same modules works (tests, in-place re-runs).ChloeKernelDebugUserIdsallowlist: comma-separated UserIds restricting the live-server debug surface per user: panel attach, the NET/log mirrors (now per-recipient sends, neverFireAllClients), and the SimControl intent.- Teardown across the board: ProjectileClient
destroy()(+ client-side safety TTL and serial-wrap recycling), ReplicaClientunlisten()/destroy(), ImGuiWindow:destroy()(drag connection severed), Panel/NetPanel detach handles, Effectsdetach(), Zonesdestroy(), ErrorWatchdestroy(), Matchmaking destroy now unsubscribes everything. - LiveConfig override deletion:
set(key, nil)removes the key from the shared document and every server reverts to its default on the next poll (previously a removed override stayed stale until restart). See LiveConfig. - Player records persist
Meta.UserIds, forwarded by DataStoreBackend toUpdateAsyncas the metadata Roblox uses for GDPR erasure tracking. - CI: rojo build smoke test and luau-lsp strict type analysis (non-blocking until the
(self :: any)escape hatches are typed away); lint paths widened to all ofsrc; leftoverHello.luauscaffolding removed. - InputDriver multi-key bindings:
Keyboard/Gamepadaccept oneEnum.KeyCodeor an ARRAY of them, so one action can ride several physical inputs;rebindandgetBindingssnapshots mirror the shape. See InputDriver. - Typed public surface for Studio autocomplete:
ServerKernelfields carry concrete types (Scheduler/Hooks/Bus/Services/NetDriver instead ofany),kernel:net()returns a typedNetDriver(the module loads at boot purely for its type; driver CONSTRUCTION stays lazy),session.Profileis typed asData.Profile, andregisterService/spawnProcessparameters are annotated. - Intents/requests are now fail-closed on a MISSING validator as well as on a failing one. A channel with a
Handlerbut zero validators rejects every payload unless explicitly marked{ Open = true }, closing the footgun where an unguardedGrantCoins/SellItemintent let a client mint currency or set a stat at will (an absent validator used to mean “accept anything”). DeclaringValidaterules counts as a validator;Open = trueis the audited escape hatch for genuinely no-auth channels (payload-less toggles, idempotent ready-ups). Surfaced three ways: a one-time[NetDriver] ... fail-closedwarn naming the channel, anUnguardedverdict in the NET panel, andNet:auditValidators()for a proactive post-boot sweep.Registrychannel defs gainedOpentoo. Existing kit/Registry channels are unaffected (they all register validators); only handler-bearing channels you never guarded change behavior, in the safe direction, visibly, in dev. - The whole tree type-checks clean (1116 → 0 luau-lsp diagnostics) and the CI analysis step is now BLOCKING: Studio’s script analysis shows zero squiggles across the framework. Fixes were annotation-only: named option-type locals for the
options or {}union trap,ImGui.Ui-typed render callbacks,:: { any }on the deliberately heterogeneous Static/Enums templates, expression-level casts where the old solver loses refinements. Spec files moved to--!nonstrict(their fakes are deliberately partial doubles; the framework itself stays strict). luau-lsp pin corrected to a real release (1.68.0). - NET inspector shows per-argument wire types: pinning a row now lists each argument’s wire type, and
"Any"arguments expand to the adaptive engine’s actual best-fit pick (Any:U8,Any:F32,Any:Table{Wave:U8}, …). Verify at a glance that values ride the optimal width. Mirrored server entries carry it too (NetTap.describeWireis public). - Rewind’s
MaxFutureSecondsdefault raised 50ms → 200ms: realGetServerTimeNowskew plus frame timing on rough connections could push honest stamps past 50ms and reject asFutureTimestamp; a future stamp only clamps to the newest sample, so the looser default costs nothing. See Rewind. - Live servers no longer flood the console with “Memory tracking is currently disabled” warnings: the panel’s Total/Script-memory rows and the diagnostics MemoryMb attribute now check
Stats.MemoryTrackingEnabledfirst (live servers run with tracking off; Studio always tracks); the panel shows a dim explanatory row instead and the SERVER window omits the bar. - Hitbox visualization (
ShowHitboxesattach option /lag:showHitboxes(enabled)): everycastRaydraws the rewound capsules it tested (green for the hit, red for tested-and-missed) as short-lived server parts every client sees. Visual only, zero cost when off.Rewind.drawHitboxis public for game tooling. - Projectiles capsule mode: setting
Hitbox = {Radius?, HalfHeight?}on a projectile definition switches PLAYER hit resolution from raw character-geometry raycasts to the same capsule hitboxes the hitscan path uses (consistent per-weapon forgiveness knobs), and the landing step draws the tested capsules through the attached Rewind’sShowHitboxesdisplay. Definitions withoutHitboxkeep the legacy behavior exactly; in capsule modeOnHitreceives{ Position, Instance }instead of an engine RaycastResult. - Lag-compensated hit DETECTION with per-weapon hitboxes:
Rewind:castRay(shooter, origin, direction, range, timestamp, hitbox?)casts a shot against every tracked player’s REWOUND capsule (pureRewind.rayCapsuleunderneath, spec-verified). Previously only the CLAIM was lag-compensated while the actual raycast ran at live positions, so high-ping shots missed strafing targets the shooter had dead-on. WeaponKit uses it wheneverconfig.Rewindis attached, with per-weaponHitbox = { Radius?, HalfHeight? }sizes (default 1.6/2.4); world geometry still blocks at its current state: walls don’t rewind, and live character bodies never block shots aimed at where targets USED to be.
Security
Section titled “Security”- Decode paths hardened against hostile bytes (wire and storage): length prefixes are bounds-checked against the remaining payload BEFORE any allocation: a 5-byte payload with a crafted U32 prefix could previously force a multi-GB
buffer.createon the server. Adaptive table decode is depth-capped (48), unknown packet/adaptive-type/enum/static ids error with clear messages instead of nil-indexing, VLQ reads cap at 8 continuation bytes, and the client receive loop is pcall-isolated like the server’s. - BusBridge reflection closed: a topic whitelisted in
ClientTopicsthat also matched aServerTopicswildcard reflected the serverSessionobject (plus attacker-chosen args) to every client; client-originated publishes never auto-forward down anymore.
Fixed (data safety)
Section titled “Fixed (data safety)”- HttpBackend treated 404 as success on every method. A misconfigured BaseUrl or changed route made every save report OK while writing nothing; 404 now only means “new key” on GET, and PUTs carry
If-Matchwhen the API returns an ETag so a concurrent writer loses the CAS instead of last-write-wins. - A player leaving during a slow profile load orphaned the profile forever (the session teardown had already run,
bind()threw, autosave kept the lock fresh, and the player was kicked on every rejoin until the server died); the load now releases immediately when the session ended mid-flight. - Autosave ran inline in the scheduler step, stalling every other kernel task (projectiles, sweeps, zones) for the duration of DataStore round-trips including retry ladders; it now runs off-thread, paces saves across a quarter of the interval, and never overlaps itself.
DataDriver.newassertsLockTtlSeconds >= 2x AutosaveSeconds: the autosave is the lock heartbeat. - One in-flight save per profile: autosave,
save(), andrelease()could interleave retry ladders, landing an older snapshot after a newer one or re-locking a key the release just unlocked; release deactivation now happens inside the mutex. - A failed final save was dropped silently; it now retries in the background (5/15/30s) before giving up loudly, and
releaseAllbails before BindToClose’s 30s hard kill with a warn naming every unsaved key. - Schema downgrades refuse to load: a rolled-back deploy no longer runs old code over newer-schema records, and the refusal is non-recoverable, so it can never trigger a backup restore over newer data.
- Receipts could acknowledge an unsaved grant: a re-invocation during the save window hit the ledger check and returned
PurchaseGrantedwith zero persistence (a crash then lost a paid grant); acknowledgement now requires a landed save on every path, grant handlers roll back partial mutations on error, and the dedupe ledger default grew 50 → 200. - Transactions: replay ids persist in
Data(written atomically with the trade itself) so DuplicateTransaction protection survives rejoin/server hop; a throwingsave()can no longer leak both InFlight locks. findUnserializablerejects sparse arrays and non-integer numeric keys (they do not survive a JSON round-trip); MemoryDriver’s buffer probe no longer condemns the driver to base64 on a transient error, andgetRangereports a corrupt entry instead of throwing.
Fixed (kernel + networking)
Section titled “Fixed (kernel + networking)”- Process suspend/resume race double-stepped forever: resuming before the queued step drained scheduled a second copy and both chains re-enqueued themselves; a
StepQueuedflag keeps exactly one chain. Self-kill no longer abandons the suspended thread unclosed or double-finishes as Completed; the PID registry is weak-valued so dropped processes collect. - Scheduler recurring tasks no longer backlog under budget starvation. A starved 0.1s task could queue ~10 copies that ran back-to-back the moment budget freed, exactly the burst the snap-forward comment claimed to prevent.
- Predicted-intent acks ride AFTER the handler with error isolation: “accepted” now means “applied”: a throwing handler acks a rollback instead of leaving the client’s optimistic effect diverged from a server that changed nothing. Request handlers pcall to
RejectValue, and a timed-outinvokeresumes withRejectValueinstead of nil (one failure encoding). - Rate-limit buckets keyed to departed Players could be recreated after session cleanup (Packet dispatches deferred); the session check now precedes bucket creation.
Registry.predict()builds one handle per channel per client. Repeated calls previously shared the ack channel while running separate sequence spaces, so an ack for one handle resolved (or rolled back) the wrong handle’s entry.- MessageDriver’s 1KB guard measures the base64 payload. Capping raw bytes passed 769-950-byte messages the service then deterministically rejected after burning the whole retry ladder.
- Replica interest joins converge every view: pending sub-threshold quantize drift flushes to existing subscribers when a snapshot is taken (the documented “threshold is the maximum drift” bound previously degraded to ~2x for joiners), same-tick joiners skip the duplicate delta, and no-op scalar
set()s stay off the wire entirely. - HookRegistry fires over a snapshot. A handler unregistering itself mid-fire shifted the live array and silently SKIPPED the next handler; in a fail-closed validation chain that is the wrong failure mode.
- ErrorWatch requires Logger relatively (a cloned kernel silently shared the original’s Logger), prunes its dedup table (interpolated error messages each minted a permanent key), and stores its connections.
Signal:destroy()resumeswait()-parked threads instead of leaking them suspended forever;Pool:release()errors on a double release (two acquires could share one Instance);expect().toEqualsurvives cyclic tables; a throwing InputDriver handler no longer suppresses the action’s Bus publish; Bus stops caching matchless dynamic topics and clears its wildcard cache on destroy; Logger sink dispatch reuses one thread instead of allocating a closure + coroutine per log line per sink.
Fixed (serialization)
Section titled “Fixed (serialization)”String/Bufferlength prefixes are enforced in both engines. A 300-byte value previously wrapped its U8 prefix to 44 and silently desynced the whole stream (documented as “max 255”, never checked).- Adaptive fractional numbers take the F32 form only when it round-trips exactly.
decode(encode(0.1))now returns 0.1 (F64), keeping the “smallest width that holds the value” promise honest. Static1/2/3error on unregistered values both ways instead of writing index 0 and decoding to a silent nil;EnumItemerrors clearly on unknown types/values;UDim/UDim2Scale clamps at the S16 field’s range instead of overflowing; the Packet factory errors past 255 channels (U8 wire ids would silently collide); the shared-cursor no-yield invariant is documented at both engines.
Fixed (gameplay)
Section titled “Fixed (gameplay)”- Matchmaking dequeues tombstone their queue entries (MemoryStore queues can’t remove by value): coordinators on every server filter reads against the tombstone map, consume poisoned reads, and re-queue live members. No more undersized matches built around ghosts. Match teleports retry with backoff and re-queue stranded members, publishing
Matchmaking.TeleportFailed. - SoftShutdown covered no reserved servers: Matchmaking match servers matched the transit-server check (reserved, owner 0) and got NO BindToClose protection; the bounce decision is per-player join data now, every reserved server gets shutdown protection, and bounce/transit teleports retry.
- Movement’s teleport threshold prorates with the sample gap. A server hitch no longer reads a fast-but-legit runner crossing 80 studs over several missed sweeps as a teleport; rubberbands preserve the character’s facing.
- Rewind buffers reset on respawn and on
AntiExploit.Forgive. A buffer spanning death → spawn interpolated between the two and rejected honest post-respawn shots as OriginMismatch. WeaponKit anchors the muzzle to the shooter’s REWOUND position (ping x move speed no longer eats the 8-stud origin tolerance) and consumes the fire-rate window only when the whole validator chain passes. - CheckpointKit no longer races the async profile load: the stage field initializes lazily per touch instead of eagerly at join (where it pinned session data and crashed the validator once the profile attached without the field); progress earned during the load window merges into the profile on
Kernel.ProfileLoaded. - Effects state is per-instance. The Simulation showcase attached a second Effects to the real kernel and its sweep removed (and its recompute clobbered) the game’s own effects; ActorPool surfaces worker require failures (
CKDeadattribute + clear dispatch error) instead of burning a full timeout per dispatch forever, and guards dispatch-after-destroy.
Fixed (ImGui)
Section titled “Fixed (ImGui)”- Stale-click delivery across relabeled buttons: widget slots are reused by call order, so a dynamic button list could hand a pending click to a DIFFERENT logical button on the next render (mode buttons relabel exactly like this). Buttons now drop pending clicks when their label changes.
Performance
Section titled “Performance”- Projectiles capsule mode hoists per-tick work out of the projectile loop: one world-pass exclude list and one alive-target snapshot per step, shared by every projectile (was a rebuild plus a
FindFirstChildsweep per player per projectile per tick), and the default raycast reuses a singleRaycastParams, reassigningFilterDescendantsInstancesonly when the list identity changes. - Process steps stopped allocating a queue entry + handle per resume: new
Scheduler:scheduleEntryre-enqueues one caller-owned entry per process for its whole life (the handle was never used). A/B under HighIntensity: GC alloc 4.4MB/s -> 0.6MB/s (7x) with step times statistically identical. Bus:publishno longer allocates on unmatched topics (shared frozen empty match) and the wildcard match cache is bounded at 512 entries (reset-and-repopulate past the cap), so dynamic per-entity topics that match a broad wildcard cannot pin one cache entry each forever.- Replicas of the same tick rate share ONE recurring scheduler entry. The scheduler walks every recurring entry every frame, so a hundred 10Hz replicas now cost one slot in that scan, not a hundred.
- Rewind samples cache the HumanoidRootPart per character (
FindFirstChildon spawn/respawn, not SampleHz times a second per player); Zones sweeps build one character include list per sweep with one reusedOverlapParams;Logger.emitearly-outs before building the entry when the console is off and no sinks exist. - Process stepping no longer allocates a closure per resume. Every yield re-scheduled via a fresh anonymous closure, which missed the profiler’s function-identity cache on every step (full
debug.info+ string cost, the same class the 0.2.0 profiler fix addressed) and fed the GC a closure per process per frame. One stable step closure per process now: under the HighIntensity simulation (200 entities + bolts) server step time dropped 1.33ms → 0.50ms and GC alloc fell ~20% (4.9 → 3.9MB/s). - Hot tasks attribute process time to the process, not the kernel. New
Scheduler.setTaskOrigin(fn, label); Process registers each step closure asProcess <name>, so the panel now readsProcess SimBolt 19.8ms/sinstead of lumping everything intoProcess:89, which also revealed the real cost driver in the sim (boltPart.CFramewrites, 3× the cost of 200 pure-math entities).
Changed
Section titled “Changed”- License: private access. ChloeKernel access is granted individually by Chloe and is revocable; granted users may not share the source or grant access on her behalf. Game-use grants, required CryptedChloes attribution, the no-redistribution rule, and the vendored Packet exclusion are unchanged. Public docs live in ChloeKernel-Docs; the docs being public grants no framework access.
- README rewritten in reference-documentation tone: introduction/pitch phrasing removed, stale spec counts fixed, and examples reworked to demonstrate features per config field (neutral names, annotated gates) instead of telling game stories; the intent example now shows schema/rate-limit/validator-chain/handler explicitly, and the kit examples document what each field enforces.
- Deferred was invisible during overload:
TasksDeferredwas reset at step start and published by a task running INSIDE a step, so it structurally read zero exactly when overload happened (and under hard saturation the Background-priority diagnostics task stopped running entirely). Counts now finalize at step end (readers always see the last completed step) and SUM into the diagnostics window; the diagnostics publisher andKernel.Overloadstreak moved to Kernel priority: health reporting must survive the very overload that starves Background work. - Server step diagnostics no longer phase-lock:
Diag{Role}StepMspublished a single last-step point sample; at a 1s diagnostics cadence against 0.1s recurring work the sample aligned with the same frame type for a whole session (reading ~35% of budget one boot and ~180% the next with nothing changed). Now a windowed average plus the newStepMaxMs. - Vendored Packet (marked mod #5): the server flush loop is pcall-isolated. A
FireClientthat throws (a player disconnecting inside the PlayerRemoving cleanup window) killed the while-true flush thread and silently stopped EVERY outgoing packet for the rest of the server’s life. - Effects/Zones sweeps collect first, fire after: expiry removes and enter/leave publishes ran synchronously mid-iteration, so a subscriber that re-applied an effect (or added/removed zones) inserted keys into the very tables being iterated: undefined behavior in Luau iteration.
- Transactions commit in place, preserving table identity:
profile.Datawas swapped to a new table on commit, silently orphaning any reference game code held to it (or any cached subtable); commits are now deep in-place merges, so every identity survives the trade, and rollback restores from a pre-commit deep backup. - Prediction acks
task.canceltheir timeout thread instead of letting it idle out the window; ReplicaClient pending never-listened replicas are capped at 64 with oldest-first eviction and a warn. - Logger/ErrorWatch specs disabled the Logger console sink and never restored it, silencing ALL console logging for the rest of any Studio session that ran the boot suite (found by the simulation harness’s missing heartbeat).
- Vendored Packet (marked mod #3): the
Anyengine’s string/buffer writes used a U8 length prefix, so values over 255 bytes wrapped the length byte and silently corrupted the stream. Long values now use new type ids 29/30 with a U32 length; short values keep the original framing (stock payloads decode unchanged). - Packet F16/F24 halved values at power-of-two boundaries (mod #4): the writers put the rounded mantissa straight into the bit field, so any value whose mantissa rounded up to a power of two (63.99, 127.99, 255.99; observed as health values collapsing:
63.99 -> 32) overflowed the field to 0 with an unbumped exponent and decoded at HALF its value;Vector3F24components were equally affected. The carry now bumps the exponent (63.99 encodes as 64), clamping at each format’s max. Sub-normal magnitudes (below 2^-14 F16 / 2^-30 F24), which previously wrote a negative exponent field, flush to zero.
Released 2026-06-09.
The gameplay-and-tooling release. Everything verified live in Studio play sessions; 196 specs at every boot.
- Replication: ReplicaService/ReplicaClient, with interest management, per-replica tick rates, field-indexed deltas (
Serde.struct). See Replica. - Gameplay: RoundKit (phase state machine), Projectiles (server-simulated, lag-comp validated, pooled client visuals), Effects (stacking buffs/debuffs with a generic stat pipeline), Prediction (instant client actions with server ack/rollback).
- Networking: unreliable state channels, predicted intents, configurable Packet rate limit with flood reporting.
- Utilities: Pool, Zones (leave-before-enter region events), Leaderstats, ErrorWatch (deduplicated error telemetry, now covering kernel task crashes), LiveConfig (MemoryStore-backed feature flags).
- Scheduler: engine-phase schedulers (PreSimulation/PostSimulation/PreRender), drift-free recurring tasks, per-task hot-origin profiler (~33ns/task) with MicroProfiler zones (Studio default),
queueDepths()/topTasks()introspection. - Serde: auto-coercion (round/clamp/parse what’s castable, fail fast on garbage), struct delta codecs.
- Debug panel (F8, Studio-only): separate CLIENT/SERVER ImGui windows, health-graded stats, diagnostic hover tooltips, hot-task pinpointing by script:line, copyable stat dumps. See Debug panels.
- PerfGuard spec: order-of-magnitude perf regressions now fail the boot suite.
Changed
Section titled “Changed”- Framework renamed ChloKernel → ChloeKernel.
- Demo removed from the repo; Bootstraps are clean templates.
- Network-ownership guarantee documented: the kernel never reassigns character ownership.
- Profiler overhead regression (2848ns → 354ns/task).
- ReplicaClient unbounded pending-delta buffering.
- Rewind clock incompatibility with client timestamps (
GetServerTimeNow). - Projectile time dilation under deferred sim ticks.
task.wait(0)frame-cost in retry backoffs.
Released 2026-06-09.
Initial release: microkernel (scheduler/processes/services/IPC/hooks), server kernel with sessions, DataDriver (session locks, migrations, codecs), MemoryDriver, Serde, NetDriver (intents/states/requests), anti-exploit (movement + lag-compensated rewind), commerce (exactly-once receipts, atomic trades), InputDriver, SoftShutdown, Matchmaking, ActorPool, TestKit + Bench, genre kits (Weapon/Spell/Checkpoint).