Skip to content

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.

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.Pricing options Retries (default 2) and RetrySeconds (default 2) for reading a pass’s list price again at purchase time, and the RobuxSpent source "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 in ReceiptLedger.Unannounced until 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 restoreAt staging, the autosave refresh reported success (or autosave 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 a discard() whose write failed, save() committed the staging anyway; it now refuses, and discard(), 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, and Prices reports only passes on sale. Commerce.RobuxSpent fired while DataDriver was still loading the buyer’s profile, so a handler writing to session.Profile errored; spends now wait for the profile.
  • Prices Report and InventoryClient.new needed a NetClient, and the README passed a name that reads like a controller’s self.Net (the Registry handle table). Report = true and InventoryClient.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: debugAllowed checks CryptedChloes (7241279999) itself, so emptying or replacing NetTap.OwnerUserIds at 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 probed supported() at load, and the HairKit character-pass spec reached the probe through rig() on a MeshPart with no mesh. The MaterialKit live-engine specs now run only when ReplicatedStorage has ChloeKernelMeshApis = true, and HairKit.rig returns (false, "the MeshPart has no mesh") for a part with no mesh before probing. See HairKit.
  • 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.RobuxSpent counts every pass bought in the server; Passes only preloads list prices (the docs now say so).
  • A place that wants the MaterialKit live-engine specs in its Studio boot sets the ReplicatedStorage attribute ChloeKernelMeshApis to true after turning on “Allow Mesh & Image APIs”.
  • Commerce.RobuxSpent fires 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:bind on 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_Report rejected; Prices no longer sends one.

Released 2026-09-30.

  • Profile:discard() drops a restoreAt staging: 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_Fetch request returns the caller’s own bag, and InventoryKit:sync(session) re-sends it. Client module ChloeKernel.Net.InventoryClient listens to INV_Sync from boot, caches the latest bag and has fetch(), get() and onChanged(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.Prices reads the local player’s own price for each pass and developer product (price, base price, discount list, Plus percent) and reads them again when HasRobloxSubscription changes. Server Commerce.Pricing mirrors Plus on session.RobloxPlus (Bus Commerce.PlusChanged) and publishes Commerce.RobuxSpent(session, kind, id, amount, source): products from the receipt’s CurrencySpent, passes from the client’s reported quote capped at the list price, else the list price. Commerce.PurchaseGranted gains currencySpent as its fifth argument. See Pricing and Roblox Plus.
  • NetTap.OwnerUserIds is 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 restoreAt staging is committed only by save() or release(); 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 OnUnequip never ran and the Tool stayed held. The merged stack keeps the equip.
  • PartyKit: with Intents = false on a networked kernel, the first invite errored because PT_Sync and PT_Invited were 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.server never 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 restoreAt staging. 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, and Registry.server installs 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 = {} left Slots nil, so count, space and grant threw and the INV_* validators rejected every intent. The after-load send only happened when session data had been touched first, and the merge subscribed through Bus:subscribe, so a kit attached after a profile loaded never sent it. _store fills in missing tables, and the merge is a Milestones.on subscription that always sends the loaded bag.
  • A channel with Validate = {} now rejects every payload unless a validator hook allows it, or it is Open = true.
  • Registry.define errors on a Priority outside "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) with tools/engine.py for offline lookups (signatures, security, thread safety, deprecations and replacements, search, typo suggestions, --full entries), and nine fact-checked domain guides in docs/agents/Engine/ indexed by docs/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.Policy loads PolicyService rules per session and fails closed until loaded; LootKit tables marked Paid = true and Transactions.atomic refuse with PolicyRestricted. ChloeKernelServer.TextFilter (broadcast, forUser) filters player-written text and fails closed; Settings string rules take Filter = "Broadcast". See Policy and TextFilter.
  • AntiExploit.Consequences bans through Players:BanAsync with escalation from ban history and the API limits enforced at the call; Bus AntiExploit.Banned/BanFailed/Unbanned/UnbanFailed. See Consequences.
  • Commerce.Passes (per-session game pass ownership, Commerce.PassOwned); Receipts binds a catch-all through MarketplaceService:BindReceiptHandler where available and gains destroy(); AchievementKit BadgeId awards platform badges under the documented rate and reconciles them on load. See Passes.
  • ServerKernel close phase: one BindToClose publishes Kernel.Closing(reason) and runs service stop() in reverse boot order within 25 s. Sessions carry JoinData, User and ExitReason (also the third Kernel.SessionEnd argument).
  • TestKit Gate: one serializable spec, audit and fuzz report that ends StudioTestService harness sessions with EndTest; tools/plugin/ChloeKernelSpecs.server.luau runs 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? }) and driver:restoreAt(key, unixMs); load(key, { UserIds }) tags the first write. MessageDriver Compress = true (EncodingService Zstd) and usage(). LiveConfig forPlayer(session) for per-player experiments. See Version history.
  • Matchmaking enqueueGroup(players) (parties queue as one item) and Matchmaking.serverAttributes(kernel, map) over MatchmakingService; PartyKit follows Roblox platform parties (Player.PartyId); SoftShutdown publishes Kernel.RestartScheduled and can migrate ahead of a scheduled restart (MigrateLeadSeconds); Leaderboards scores(board, subjects) (BatchGetAsync) and names(entries) (bulk display names).
  • Client: Highlights keeps Highlight instances under the engine’s 255 limit; ProjectileClient and HazardClient OnAcquire/Linger with Pool.clearEffects/Pool.quietEffects; TagWatch limits tag membership to instances in Workspace. See Highlights and TagWatch.
  • InputDriver Priority/Sink, Input.PreferredChanged and :preferred(), InputDriver.parseKeys and InputDriver.label, touch button Title/Image/Position. DeviceBench RespectSavedQuality. ImGui ui:section.
  • AnimKit rig:setParameter and rig:setLod; NPCKit archetype RagdollOnDeath, Description, AgentParams, Traverse and Bus Npc.PathLink; Pathfinding follower({ AgentParams }) with Info.Link for PathfindingLink crossings; InventoryKit Tools (Tool and Backpack bridge); Movement.teleport streams the destination before pivoting; MeleeKit and Sweeps RequireLineOfSight.
  • Analytics economy, funnel, onboarding, progression; Text Source = "Translator"; Preload Concurrency; CutsceneKit Suspend; Impact ReducedMotionScale; VfxSuite VfxDensity and suite:stop(timeline); Scope:tween; Rng:index; ActorPool.shareContext; Logger.setStructuredConsole; InteractionKit prompt presentation options; TeamKit:unassign; CheckpointKit TouchDebounce; Zones KeepQueryable; Procedural.Failed.
  • Steer projectiles mid-flight. Projectiles:forEach visits live shots, redirect(serial, {Velocity, Position?, Owner?, Lifetime?}) restarts a shot’s flight from new data and can transfer ownership, and retire(serial, position?) removes a shot without callbacks. Redirects ride a new CKPJ_Redirect packet; ProjectileClient blends the visual onto the new path and calls the optional OnRedirect hook. 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.md rulebook (Claude Code reads it through CLAUDE.md) and a Game layer in src/Shared/Game, src/Server/Game and src/Client/Game that the Bootstraps load between boot and start (channel handles injected as self.Net, ComponentService/ComponentController over Mounts, UIController over 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 (generates docs/agents/Catalog.md), tools/setup.py, docs/agents/Recipes.md with 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), which Registry.client uses for State channels; NetDriver:markTapDirty() and a GetPlayers option on NetDriver.new that 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, then ConfigSnapshot.UpdateAvailable refreshes it and publishes Config.Changed, with no polling, MemoryStore or MessagingService. Only keys declared in Defaults are 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() and update() 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 topic Data.Throttled(key, errorText), published once per throttling episode by an attached driver.
  • MemoryDriver: sort keys. set(key, value, ttl?, sortKey?); SortedMap update transforms receive and may return a sort key; getRange(direction?, count?, exclusiveLowerBound?, exclusiveUpperBound?) returns sortKey in each row.
  • Leaderboards: option MaxWriteAttempts (default 10), Stats.Dropped, and an optional backend budget() hook.
  • Analytics: Stats.DroppedFailed; a custom Destination may return the number of events it delivered.
  • Pathfinding: find with Method = "Roblox" also returns the Path and a set of jump waypoint indices.
  • NPCKit: archetype option ClientPhysics = true keeps the engine’s automatic network ownership for that archetype. npc.Dying is true between Npc.Died and despawn.
  • QuestKit: Objective.ActorArg counts an event only when that argument position is the session or its player.
  • RoundKit: stop(), timeLeft() and an optional Clock.
  • InventoryKit: destroy().
  • CheckpointKit: TouchSlack (default 1.5) and a Pad field in the Intent.Checkpoint context.
  • Gait: Avatar Joint Upgrade rigs. The default root joint is the root part’s AnimationConstraint, then its Motor6D, and RootJoint accepts either.
  • MoveKit: attach option Player (the tracked player, default Players.LocalPlayer on clients). Time: attach option FrameClock (frame-time source, default time()).
  • 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) and Data.LockLost(key) Bus topics, LockSkewSeconds (default 60) on DataDriver, Session.bindLast and session.ProfilePending, Kernel.Stopped(reason) after the close stop phase, Policy:settled(session), InventoryKit:space(session, id) and Bus Inventory.Overflow, Bus Commerce.TransactionEnded(a, b, ok), CraftingKit WallClock and Field, and an optional deadline for DataDriver:releaseAll.
  • Regression runner tools/regressions/datasafety.py (44 cases; python tools/check.py step datasafety): 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.Handoff hands 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. Config Handoff (default on for live servers on a shared backend; false turns it off). The data safety runner gains _data_handoff.luau, multi-server cases on a virtual clock whose timers fire. See Handoff.
  • Unreliable state carries a 2-byte sequence footer and clients keep only the latest datagram per stream (Ordered = false opts out); onUnreliableState errors 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 one FireAllClients while 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 (KeepQueryable opts 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 PromptTriggered connection, mirrors LineOfSight in RequiresLineOfSight and rejects triggers on disabled prompts. TeamKit spawns through RespawnLocation and 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.PurchaseGranted carries ProductPurchaseChannel.
  • 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 MountType set 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 VfxDensity and follows kernel time scale; AudioKit starts layer stems together once loaded; Preload runs batches concurrently.
  • ProjectileClient and HazardClient move BasePart visuals with one BulkMoveTo per frame; CameraKit writes Camera.Focus and follows workspace.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 MoveTo on 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.HeartbeatTime and Stats.PhysicsStepTime (seconds) where available; the deprecated *Ms properties remain the fallback.
  • Spatial: Spatial.characters follows CharacterAdded, CharacterRemoving and PlayerRemoving and samples root positions only while a root is tracked (it polled every player forever). Characterless players drop immediately.
  • Simulation: Simulation.server waits on the kernel’s boot attribute instead of polling every frame; Kernel.shutdown clears 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.client constructs State channels immediately (through NetClient:declareState), so their frames decode before .on() is called; frames that arrive before .on() are not delivered. Channels built lazily (ReplicaClient, SettingsClient, a direct NetClient: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.FloodDetected is coalesced to one report per player and reason per heartbeat window. The bus payload gains reason, count; the hook payload gains Reason and Count; PayloadBytes is the bytes dropped in that window. Packet’s RateLimitExceeded fires (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.PreRender and connect only while something is live. Static hazards set their CFrame once at spawn.
  • RagdollClient: release waits for every cut joint’s Enabled change 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.ProfileLoaded and 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:ReserveServerAsync instead of the deprecated ReserveServer.
  • SoftShutdown: waits for departures on PlayerRemoving with 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 set and update, 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 explicit PollSeconds is 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 Linger seconds after the last occupant leaves, using a per-stage timer that re-entry cancels. The 1-second polling loop is removed; SkipLoop specs still call step().
  • 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/IncludeInstances instead of FilterDescendantsInstances (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 PhysicsService on older engines.
  • SpatialKit: hasTag uses Instance: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 MaxMana after a cast, and CompanionKit follows only while a companion is summoned.
  • CompanionKit: Companion.Died and the Aura removal happen at the companion’s death (from Npc.Died), not after DespawnSeconds.
  • ShopKit: runs one buy or sell per session at a time; an overlapping one rejects Busy.
  • DialogueKit: choose and advance return Busy while a handler yields.
  • ProceduralKit: owned spawns share one session binding per owner.
  • RoundKit: TickSeconds now 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>" at Enum.RenderPriority.Camera.Value + 1 while it holds the camera (read RenderName). The shake always lands after the player camera and CameraKit, and neither module keeps a permanent RenderStepped or PreRender connection.
  • 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() and scaled() from anchors on read (same values, no Heartbeat connection).
  • AudioKit: writes SoundService.AmbientReverb only 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, attachRig waits 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 PreSimulation and skips the root write while the rig’s Animator reports EvaluationThrottled.
  • VfxSuite: releases spawns that have no Lifetime once 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 RaycastParams per 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.attach returns a handle with detach() (still truthy) instead of true.
  • ImGui and HookScope: ImGui windows connect UserInputService.InputChanged only during a drag. HookScope records a fire without allocating a table.
  • Framework warnings go through Logger.scope(<Module>) instead of raw warn, so sinks (the F8 SERVER log, analytics) see them and Logger.setLevel and setConsoleEnabled apply. 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 raw warn.
  • 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.spawn reports 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. A Loot.Refused or Inventory.Overflow for the buyer during the grant declines it too unless the grant returns true. Receipts waits for the buyer’s Policy lookup and for a trade on the buyer’s profile instead of deferring, and a second Receipts.attach on 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. restoreAt stages 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 Take yielded. InventoryKit count and take hold back one unit of a slot whose OnUse is 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 Validate checks 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.
  • RoundKit: the undocumented TimeLeft field and _tick. Use timeLeft().
  • Options that no longer do anything and are ignored: CraftingKit TickSeconds, PartyKit TickSeconds and SkipLoop, ProceduralKit FlushIntervalSeconds.
  • 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 with AnimationClipProvider: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 BreakJointsOnDeath off, Studio disables every AnimationConstraint before Humanoid.Died fires, so Ragdoll found no joints and refused the body, and NPCKit RagdollOnDeath and AutoDeath left the engine’s untuned limp rig (no limb collisions, no-collides, socket limits or Ragdoll.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 UIDragDetector takes mouse and touch presses, so the button’s own Activated never fires. A detector press that ends within 4 pixels of its start now toggles collapse, Activated still 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 WrapLayer under a plain Part (the engine parents it only to a MeshPart), table doubles in OverlapParams.ExcludeInstances (the engine keeps only Instances), NetTap capture assumed off (a Studio server starts with it on) and engine teleport options built with TeleportOptions: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.fire now returns a crashing validator’s traceback as a third value, Fuzz reports it in ValidatorErrors, 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 or Open = true, and securityAudit/auditValidators report them.
  • The Simulation SimNoise debug intent had no validator, so the Studio audit rated it High on every Play; it now validates with NetTap.debugAllowed.
  • Projectiles stepped the live table while OnHit, OnExpire and Projectile.Hit subscribers 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, ChloeKernelServer and ChloeKernelClient from 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_Play carries a has-origin flag.
  • Packet’s NumberVlq writer 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 no destroy(). They unhook their validators and their handlers go inert, including behind a game validator; WeaponKit and SpellKit stop() do the same. CompanionKit keeps and disconnects its session hook.
  • Replica:bindZone never 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, or LockWaitSeconds when unset; load() takes HandoffSeconds.
  • 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 CreateEditableMeshAsync returned nil; CutsceneKit skip() 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 Version above 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 DataDriver Codec = "Auto", infers every number as NumberF64. Integer defaults were typed NumberU32, 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 *Async call) is resumed by the engine, not early by the scheduler; its next Process.yield() hands it back. Completion and errors after an engine resume are reported, kill() cancels the pending engine resume, and resume() never double-resumes a waiting body.
  • Spatial: a NaN position removes the id from the index instead of corrupting it, and cannot abort Spatial.characters refreshes for later players.
  • Signal: disconnect is O(1) as documented (it was a list walk); Signal:wait never 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 MountType values 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: acquire counts 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.Overload is raised before attributes are written, so a failed attribute write cannot suppress it; DeferredKeys is 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 Instance slot (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. defineRequest errors at definition when RejectValue does not encode under the Response schema, instead of every rejection hanging the client for the full response timeout.
  • Registry: Range rules 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 nil argument 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 pending Settings_Get wait 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 Interest callback yields is no longer resubscribed (roster scans, resyncPlayer, bindZone entries).
  • NetGovernor: detach() disconnects its session listener; NG_Ack counts only echoes of outstanding probes.
  • Prediction: Prediction.wrap returns one handle per channel name across all NetClients, so two Registry.client calls no longer share an ack channel with separate sequence numbers.
  • HazardClient and ProjectileClient: a throwing OnRender, Path or PathOffset retires 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, TransportFloodBytes counts the bytes actually dropped, and a non-number RateLimitBytes attribute 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: attach waits up to LockWaitSeconds (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. enqueue reserves 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 MaxWriteAttempts consecutive windows (default 10) is dropped with a warning instead of retrying forever. MaxEntries above 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.Sent counts 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: getRange clamps count to 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 CharacterAdded handlers run.
  • Rewind: castRay no longer hits players who have no character or are dead; their history is cleared, so a live target behind a corpse is hit.
  • Ragdoll: AutoDeath works on classic rigs by setting Humanoid.BreakJointsOnDeath = false. Ragdoll state is released and torn down when a model leaves the DataModel without Destroy(), and AutoDeath Died watches disconnect on character change, session end and destroy().
  • 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: forkJoin waits for every actor before sending chunks. Readiness waits are event-driven with one timeout, per-job timeouts are cancelled when results arrive, and destroy() wakes pending readiness waits.
  • Pathfinding: the Roblox method abandons a route when Path.Blocked reports a waypoint ahead, and the follower sets Jump for 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.Died sets npc.Dying: pending melee windups are dropped, and dying NPCs are excluded from all(), targets(), update(), emitSound, squad roles and lane checks. act() and defend() return false. The despawn timer runs on the kernel Scheduler and is cancelled by destroy().
  • 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 OnUse runs. A yielding handler can no longer be used twice, drive a count negative, or delete the item that moved onto its index; moves and INV_Use for a busy slot are refused.
  • CraftingKit: crafts are re-checked after a yielding Craft.CanCraft hook and inputs are taken with rollback, so duplicate crafts return MissingInputs and inputs already taken are granted back. Cancel refunds that no longer fit publish Craft.Overflow instead of dropping silently.
  • ShopKit: honors the currency adapter’s Take result. A failed charge takes the granted items back and rejects CannotAfford.
  • DialogueKit: a dialogue stays busy while Where, Run or OnEnter yields, 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 Triggered connection 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 LocalTransparencyModifier on 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. setVolume on a stopped handle does nothing.
  • AudioKit: destroy() finishes every handle immediately, including crossfading music, and destroys playLayers stems. 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 RaycastParams and ignores the local player’s character.
  • AnimKit: a weight change during ik:release(seconds) still removes the IKControl and helper part, and destroy() during a release removes them too. Rig.destroy destroys its loaded tracks. Playing on a rig without an Animator raises a clear error. A player character that leaves the game without Destroy releases 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 bindBox overlay is removed when the original part is destroyed. destroy() puts the original mesh back on parts bound with bind() 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 bindAccessory pendulum 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 by CreateDataModelContentAsync (the call returns a result and a content) and fails with a one-time warning when registration does not succeed, for example StorageLimitExceeded.
  • 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 bindCharacter calls 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:fire and Bus:publish working. 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 Touched on 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 Defaults field no longer makes older records unreadable, and a server on the previous Defaults no 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 Defaults errors at boot.
  • A live record that the current Validate or a migration rejects, or one written by a newer SchemaVersion, 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.LockLost instead of silently deactivating the profile. Lock staleness allows LockSkewSeconds for 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 OnUse was still running could also be sold, crafted or traded away.
  • A ShopKit buy whose Take yielded 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:update retried 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 with Backup enabled 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.
  • 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 or Open = true.
  • Wire change: CKCUT_Play gained 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 = false restores the old behavior); Impact ducks Ambient; Zones parts lose CanQuery and CanTouch; tagged instances outside Workspace no longer mount or get prompts; PartyKit follows platform parties (PlatformParties = false turns it off); version-history fallback is on for DataStore profiles.
  • Signatures that changed: Kernel.SessionEnd gains an exit reason; LootKit:rollFor and :award return a refusal reason; Settings:set returns whether it stored the value; TeleportRetry failure callbacks receive reason, result, message; SoftShutdown.attach returns { migrate, destroy }; AnimKit play returns nil while the rig waits for the Workspace.
  • Games that sell paid random items or allow trading of paid items must attach Policy and mark those loot tables Paid = true; without it, Paid tables roll for everyone after one warning.
  • WeaponKit.service errors unless every MaxRange is 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.infer returns NumberF64 for integer samples. Code that relied on inferred NumberU32/NumberS32 widths 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.
  • Preload failed batches count their ids as loaded-and-failed (progress reaches the total and the ids appear in Failed).
  • 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. See docs/event-driven-networking.md.
  • Wire change: deploy the server and client Net/Packet (init.luau and Types/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 a RejectValue that encodes under it; defineRequest now errors at boot otherwise. { "Any" } responses accept a nil RejectValue.
  • Deploy the Settings server and SettingsClient together: rejections now arrive as { Accepted = false, Value } replies instead of a pushed sync plus false.
  • Net.FloodDetected consumers receive fewer, coalesced reports. Count strikes with the new count argument if you relied on one event per dropped payload.
  • Prediction options passed to a second wrap of 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.enqueue can return false, "cancelled" when the player dequeued, matched or left while the call was yielding.
  • Leaderboards with the default backend return false from submit for fractional values. Scale them, for example to milliseconds.
  • LiveConfig documents carry a reserved __CKWrittenAt field; set("__CKWrittenAt", ...) errors.
  • SoftShutdown.attach(kernel, options?): the new options are test injection points; existing one-argument calls are unchanged.
  • Under Server Authority, Movement corrections would fight the engine’s rollback. Run it in log-only mode with RubberbandStrikes = math.huge (strikes, the hook and the bus event still fire) or do not attach it. Rewind is unaffected.
  • Rewind.validateShot returns NoShooterHistory for a shooter who died before the shot was validated.
  • Ragdoll AutoDeath sets BreakJointsOnDeath = false on player humanoids. A manual on-death enable() needs the same setting.
  • NPCs are server-simulated by default. Games that relied on client-simulated NPC physics must set ClientPhysics = true on the archetype. A ClientPhysics NPC stays server-owned after a ragdoll release.
  • Code that stepped a dead NPC through npc:act() or read it from Kit:all() during the corpse window no longer sees it. Use Npc.Died for death reactions.
  • QuestKit’s default still counts an event when any argument is the session, victims included. Set ActorArg = 1 on Combat.Kill objectives to credit only the killer.
  • Custom ShopKit currency adapters must return true from Take on success; a falsy return now cancels the buy.
  • RoundKit schedules nothing before start().
  • SpellKit: code that lowers session.Data.Mana directly 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 AmbientReverb every 0.25 s must now set it.
  • VfxSuite spawns without Lifetime no longer stay forever. Give emitter spawns a Lifetime that outlives their particle bursts.
  • AnimKit:attachRig can 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_ and CKCamera_ are reserved (one per Impact and CameraKit instance; read RenderName). The internal Stepper fields of Impact and CameraKit are gone; AudioKit and AnimKit LoopHandle is nil while idle.
  • MoveKit context State can now be "Other". Custom When functions 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.Active being true at boot (Studio or ChloeKernelDebug) must open the NET window, or set NetTap.Active itself.
  • Receipts grants that return false now decline the purchase, and a kit refusal during a grant declines it unless the grant returns true. Grants that returned false for other reasons must return nothing or true.
  • A second Receipts.attach on one MarketplaceService errors. Register every product on one processor.
  • Validate rejecting a live record no longer restores an older version: the load fails closed. Relax the rule or add a migration before deploying a stricter Validate.
  • An explicit Codec that cannot encode and decode the Defaults unchanged errors in DataDriver.new.
  • DataDriver:restoreAt no longer writes the old version at load; save or release the returned profile to commit it.
  • ShopKit buys charge before granting. Custom Currency.Give adapters are now called to refund items that stopped fitting.
  • CraftingKit timed crafts write profile.Data.Crafting (rename with Field); a schema codec must hold that field or enable Extras.
  • While session.ProfilePending is true, timed crafts refuse with Loading, 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 Presence flag; servers on an older build are only taken over by lock age, so mixed-version deploys are safe. saveAll saves profiles concurrently.

Released 2026-09-05.

  • Add Scope for connections, timers, instances, cleanup callbacks and nested lifetimes. Kernel:createScope() owns scopes until early disposal or shutdown; the restricted kernel facade also exposes createScope() and after(). 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. See docs/scopes.md and docs/event-driven-networking.md for 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 MaxDecodedArrayElements attribute. Harden public rewind input checks. Fuzzing validates by default; handler execution now requires ExecuteHandlers = 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.
  • 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; after supports 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.md for remaining scaling gaps, storage costs, and profiling requirements.
  • 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.
  • Tagged releases ship two models. ChloeKernel-<tag>.rbxm (the development tree with TestKit, specs, and Simulation) and ChloeKernelRuntime-<tag>.rbxm (runtime only, built from runtime.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.server calls enableDiagnostics whenever the place opted its debug surface on (ChloeKernelDebug true), 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, oriented box(cframe, size), true-3D cone(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 answers at(x) (Linear/Smooth/Step ease per segment; Clamp or Linear extrapolation outside the keys) and solve(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 after Linger (default 10s, so stepping out and back never thrashes spawns), OnEnter/OnLeave fire per occupant, and Replicas handles auto bindZone while loaded. Occupancy rides bus Zone.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. Bus Stage.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, so float(5) degenerated to NextNumber(5, 1). The fork() 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 answering Position with its last value, so the liveness check never fired; anchor resolution now treats a nil Parent as 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: DuckBus defaults to a real bus. The default was "World", which is not one of AudioKit’s default buses (Music/SFX/Ambient/Voice/UI), so pulse() 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: PlantSmoothing actually smooths. The plant/unplant setWeight calls omitted the fade argument, snapping IK weights instantly; they now pass PlantSmoothing, and the stepping path (plants, fades, root offset) gained spec coverage. See Gait.
  • Rules: the gate order is documented. once -> cooldown -> count regardless of chain order; once() means a single fire per key ever, so combining it with count/cooldown still fires at most once (for repeated-but-throttled awards use cooldown alone). Behavior unchanged; the module header and cookbook now say so. See Rules.
  • CutsceneKit: Line publishes the raw string. The header no longer implies the kit calls Text; localization happens in your Cutscene.Line subscriber (send keys, localize at render). See CutsceneKit.
  • 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 every RemoveFace/AddTriangle shows 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, pure MaterialKit.order ranks. Local-only VFX, same broadcast stance as Dissolve. See MaterialKit.
  • CameraKit: cinematic camera control as data. Shots declare what the camera does (Static, Follow with exp-smoothed offset, Orbit, Rail = Catmull-Rom through CFrame points with eased progress and optional LookAt), 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, cut snaps, 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; pure CameraKit.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 named Rigs roster so shared definitions never hold Instances, Sound = AudioKit at the scene origin, Vfx = VfxSuite, Line = bus Cutscene.Line(text, seconds) for Text-key subtitles, custom Handlers); the server broadcasts ONE CKCUT_Play packet (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. Bus Cutscene.Started/Ended/Skipped/Line. See CutsceneKit.
  • DialogueKit: server-authoritative conversation trees. Nodes with Text-key lines, Choices with per-player Where gates (hidden choices cannot be picked; indices mean the VISIBLE list, and gates re-check at pick) and server-side Run consequences, Next chains, OnEnter node actions; every reference validates at attach. Wire: DLG_Sync state push per node ({} on close), fail-closed DLG_Choose/DLG_Advance intents gated against the session’s live node; begin() stays server-side (wire it to an InteractionKit prompt), leavers clean up without intents. Hook Dialogue.CanBegin (fail-open); bus Dialogue.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; decorates Hooks.fire and 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’s Random as a first-class object: float(min?, max?), int(min, max) (inclusive), choice(list), shuffle(list) (Fisher-Yates over a copy, input untouched), and fork(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) sets kernel.Time: now() (os.clock), server() (GetServerTimeNow, the claim-stamp clock), frame() (accumulated frame seconds, never pauses), and scaled() (game time honoring setScale and pause/resume: slow motion and pause become one switch instead of a hunt through every timer; the accumulator integrates scale×dt, never rewinds). Injectable step(dt) spec seam; bus Time.ScaleChanged/Paused/Resumed. See Time.
  • Profile snapshots. profile:snapshot(label?) → id, :snapshots(), :rollback(id): in-memory point-in-time deep copies of Data for 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’s save(). 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 through task.spawn so a throwing rule never breaks the bus. See Rules.
  • AchievementKit: persistent conditions over the Bus. An achievement is a topic, an optional Where filter, and a target Count; progress lives in profile.Data (Field default "Achievements") so it survives rejoins, unlocks fire once per player ever, Reward runs at unlock, and progress(session, id)/unlocked(session, id) read out for UI; bus Achievement.Progress(session, id, count, target) and Achievement.Unlocked(session, id). See AchievementKit.
  • Chronicle: kernel flight recorder. Chronicle.attach(kernel) decorates the live Bus.publish and Hooks.fire (restored on detach()) 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?}) resolves get(player, key) through the player’s LocaleId, 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), pure Text.interpolate for 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 with Lifetime auto-release, Emit = particle bursts on named emitters, Sound = AudioKit playAt, Dissolve = a Dissolve play on the spawn, anything else = custom Handlers); the server sends ONE CKVFX_Play packet (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 clears PostQuality: low-end devices keep the shake and duck, never the post-processing cost; pure intensityAt/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; pure tiltFor/heightOffset. See Gait.

Released 2026-07-10.

  • Projectiles: authoritative curved paths. A per-definition Path(seed, age, origin, velocity, flightSeconds?) → Vector3 evaluated identically on both machines: the server sweeps each tick’s Path(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. PathOffset remains 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), Duration auto-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); HazardClient renders 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 on Hazard.Entered, cleanse on Hazard.Left. Bus Hazard.Spawned/Entered/Left/Retired. See Hazards.
  • ProjectileClient: OnImpact carries the wire serial (ported up from Withcraft&Wizardry). OnImpact(position, velocity, serial): the serial is the same seed PathOffset receives, so impact cosmetics reproduce the exact per-shot roll the visual flew with; extra arg, old callbacks ignore it. See Projectiles.
  • 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), OnImpact receives 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 optional maxDistance (point-targeted expiry: lifetime caps at maxDistance/speed) and speedMultiplier (per-shot speed with no wire change), OnExpire receives the expiry position and the expire packet carries it to clients. See Projectiles.
  • Movement: AntiExploit.Exempt standing 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 Configuration children 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.
  • 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 ChannelSeconds and SpellKit defaults a missing value to 0 (instant) instead of erroring in beginCast; WizardDuel and GunArena clients call the intent handle’s fire(...) (the lowercase dot-call NetClient actually returns) instead of a nil :Fire method. See SpellKit, Starter Templates.
  • Rewind: the claimed fire direction now gates hits. validateShot rejects claims whose origin-to-hit vector strays past DirectionDotMin (default 0.94, about 20 degrees) from ShotClaim.Direction with reason DirectionMismatch; previously the field was carried but never read. See Rewind.
  • Studio boot gate: spec failures halt the play test. Main.server asserts 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 LogCustomEvent destination counts skipped playerless entries in Stats.DroppedPlayerless, and attaching with WatchErrors plus 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_Sync on 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 = false and a MaxSlots wire guard. A second attach on the same kernel can skip INV_* channel wiring instead of crashing on redefinition, and MaxSlots asserts 1..255 at attach (slot indices ride NumberU8; 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 = false kits destroy a leaver’s companion too. See CompanionKit.
  • README truth pass. Removed the nonexistent Npc.Idle/Npc.IdleEnded topics, Npc.Tactic documents its three real roles, channel integer coercion documented as wrapping (replica fields clamp), Boolean1 marked channel-only (replica schemas use Boolean8), BonePhysics.attach() in every example, receipt ledger default corrected to 2000, rebind examples 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 as fn(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-free radiusInto/boxInto/coneInto variants 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 a MountType attribute plus per-type attributes, and Mounts.attach(kernel, options) wires the systems at level load: Sound mounts become AudioKit playAt emitters (SoundName, Volume?, Speed?), Zone mounts feed Zones:addPart (ZoneName), BoneChain mounts 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. Handlers extend 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. Bus Mount.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), plus Spatial.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 in update() 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’s NG_Ready handshake 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 in Governor.Stats; bus Net.TierChanged(player, tier, stats); client half GovernorClient.attach(). See NetGovernor.
  • Forensics: anti-exploit flight recorder. Every player’s kinematics roll through a ring ([t, position, look, velocity, humanoidState] at RecordHz 10 for WindowSeconds 20); a watched bus topic (default AntiExploit.MovementViolation) freezes the window into a capture and records TailSeconds more; 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, last MaxCaptures readable via captures()); 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. Bus Forensics.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 most MaxInfluences bones (default 2, clamped to 4, weights normalized; root-plane vertices bind the root and never pull band centroids); rig() injects the skeleton (AddBone with ParentId/Virtual, SetVertexBones, SetVertexBoneWeights), registers the content into the DataModel (CreateDataModelContentAsync(Content.fromObject(mesh))), bakes through CreateMeshPartAsync, 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, DeviceBench MaxChains budget). 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 real AddBone call) and per-asset read permission (CreateEditableMeshAsync loads experience-owned or creator-owned meshes only; third-party catalog hair returns (false, reason)); either failure keeps the bindAccessory pendulum fallback. Bus Hair.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 DotSize voxel grid with per-cell dedup, and dots advect through a 3D Perlin field (ScatterSpeed, NoiseScale, Drift) while fading; Reverse assembles instead; Duration = nil returns a manual controller whose setProgress(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 via Reverse plays; 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 through workspace:BulkMoveTo, MaxPoints scaling 500..5000 by DeviceBench quality when unconfigured, CustomDot templates, 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 alongside src. See Starter templates.
  • Studio boot gates in Main.server. Every Studio play test now asserts zero High-severity securityAudit findings after start and runs Fuzz.run against 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.
  • API consistency: kernel-integrating modules use attach(). InputDriver.attach(kernel) and MoveKit.attach(kernel, options) are the canonical constructors; .new remains 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 for registerService.

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_Sync roster pushes and PT_Invited notifications, Party.CanInvite hook (fail-open). Parties never touch the combat gates; sameParty and members (group-queue feed for Matchmaking) are the integration surface. Bus Party.Created/Disbanded/Joined/Left/Kicked/Invited/Promoted. See PartyKit.
  • Effects is now a full status system. TickSeconds + OnTick periodic ticks (poison, regen) with catch-up bounded to 8 per sweep; Category dispel groups with cleanse(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; apply returns 0 and publishes Effects.Blocked); owners receive a CKEffects state push after every change ({[name] = {Stacks, Remaining?}}) for status-icon UI. Bus adds Effects.Blocked(player, name, byEffect) and Effects.Cleansed(player, category?, count). See Effects.
  • LootKit: weighted loot tables with persisted pity. Entries yield items (Count fixed or {min, max}), nested tables (depth capped at 8, references validated at attach), or nothing; PityAfter = N force-hits on the Nth consecutive miss with counters persisted per player in profile Data.LootPity (rollFor/award advance pity, plain roll never does); award(session, table) grants through InventoryKit and returns (granted, overflow). Bus Loot.Rolled/Awarded. See LootKit.
  • CraftingKit: recipes over InventoryKit. Inputs check atomically before any take; Seconds > 0 queues one timed craft per session with cancel() refunds; Station recipes gate on the caller’s claimed station with proximity verified in the fail-open Craft.CanCraft hook; output that does not fit publishes Craft.Overflow; fail-closed intents CR_Craft {recipeId, station} / CR_Cancel. Bus Craft.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 from Seed + floor(clock / Every) so every server lists identical stock with zero coordination; Stock = N caps buys per player per rotation window; buys grant first and charge for what fit (partial grant = partial price, full inventory = no charge); fail-closed intents SH_Buy/SH_Sell with counts capped at 99. Bus Shop.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 FollowDistance and pivots beside the owner past TeleportDistance, death cleanup with Companion.Died, and Aura holding an Effects buff on the owner while summoned; ownership gates in the fail-open Companion.CanSummon hook; fail-closed intents CP_Summon/CP_Dismiss. Bus Companion.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 in Report.HandlerErrors (assert it empty as a boot gate); validator throws count as rejects; deterministic per Seed; Driver injectable 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, OnUse consumables (return true to burn one; draining a stack auto-unequips), move drag-and-drop (swap or merge, equip references follow the item). State persists through the session profile (Persist = false for 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 an INV_Sync state push after every mutation so inventory UI is pure rendering. Hooks Inventory.CanEquip/CanUse (fail-open); Bus Inventory.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), KeepBest boards only overwrite improvements through atomic UpdateAsync (Ascending flips the comparison for speedruns), failed writes stay queued and retry next window, and top() serves a cached page (one GetSortedAsync per CacheSeconds regardless 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 declarative submit(board, key, value, keepBest, ascending) (the keep-best rule arrives as data, so an external DB applies it atomically its own way) or DataStore-shaped set(board, key, updater), plus sorted; 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; Bus Leaderboard.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 parts TeamSpawn + Team attribute; characters pivot to an owned spawn with the anti-exploit pardoned). Friendly fire off by default, enforced where damage happens: vetoes registered on Weapon.CanDamage (new fail-open hook in WeaponKit’s damage path) and Melee.CanHit; npc handles resolve through Archetype.Faction, so faction NPCs and same-named teams read as teammates. Bus Team.Assigned(player, team, previous?). See TeamKit.
  • WeaponKit: Weapon.CanDamage hook 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.
  • 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 ComboNext chains (Melee.Combo counts the string); blocking chips (BlockScale), guard-breaks shatter blocks (full damage, guard drops), and timed parries beat both: a parry press arms a ParryWindow (+LatencySlack so 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 consume ParryCooldown hit or miss, so spam can’t blanket a swing. Hit frames sweep range + facing arc with a fail-open Melee.CanHit hook 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-scaled npc: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" rides UserInputService.JumpRequest, other strings ride InputDriver Input.<Action> bus events, or kit: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) and airStep({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.server meters through the fail-closed CKMove intent with its OWN charge/cooldown books and its OWN ground probe (a client that lies about landing gets no fresh charges); Forgive = true pardons 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. MaxVisuals now 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 within ThreatRadius (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 pure ProjectileClient.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 buys ParticleBudget/VfxDensity, strong Compute buys BoneChains/BoneDistance/ProjectileVisuals, strong Resume buys AudioChannels, 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/Ultra tiers remain coarse conveniences for pick(). Client modules consume the profile when unconfigured: BonePhysics cull MaxDistance and NEW MaxChains chain budget (past it the chains nearest the camera win, the rest freeze at their pose), AudioKit MaxChannels, ProjectileClient NEW MaxVisuals (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 via OnChange(quality, profile) and Device.QualityChanged. Time-boxed means weak devices run fewer iterations in the same ~90ms instead of hitching longer; results cache, report Platform, optionally sample a median FrameMs, publish Device.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), and kit: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/Oblivious or any custom {Range, BlurStuds}), anisotropic blur (direction resolves well, distance poorly), and a Distance override so route-measured occlusion plugs in; plus inCone/canSee vision 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:moveTowards is now a thin wrapper over it and applies the point via Humanoid/injected Move. BehaviorTree stays 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: DetectionSeconds on updateTarget turns acquisition into an accumulator (fills faster up close and against fast movers, decays unseen; half charge publishes Npc.Suspicious and 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 as Npc.Damaged) suppresses aim for 1.5s (read State.SuppressedUntil to 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/ReloadSeconds force the reload pause players push into (Npc.Reload), melee WindupSeconds telegraphs 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 = true opts out). npc:faceTowards tracks 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 by MaxStepHeight, 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 new sightLine (vision, now backing NPCKit.hasLineOfSight on grids) lets eyes cross valleys and pits that block feet and only occludes on ground RISING past the band. New Pathfinding:distanceField({Grid, Origin, MaxDistance}) Dijkstra-floods route distances from one origin: emitSound Path 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 by refresh/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, and PathMethod = "Roblox" computes off-tick: ComputeAsync yields 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 sets RespectCanCollide so 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). New grid: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 on GoalOutsideGrid/GoalBlocked technicalities. The clearance check also grew a NARROW PHASE: GetPartBoundsInBox matches 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 ReactiveSelector re-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; plain Selector keeps the resume-directly semantics. tick() threads an optional clock into Cooldown nodes (pass the NPCKit kit clock for determinism under spec, without the global _setClock swap), and reset() 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, so Sounds.Topics can safely list Npc.Hitscan so 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) and Squad (auto-join a shared blackboard): members report what their senses ACTUALLY produced (sight fixes exact, heard positions blurred by the listener’s skill) via squad:report, readable through squad:lastKnown(target) / npc:lastIntel(target); the per-squad director (stepped by kit:update()) allocates roles per engaged target, publishing Npc.Tactic(npc, role) for client animation polish; your loop reads squad:role(npc) to decide who pins the last-known fix and who curves the arc. Cover is map-authorable: tag parts (SpotTag) with an optional Direction attribute 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. New Kits/BehaviorTree: Selector/Sequence composites with real Running-resume memory, Condition/Action leaves, Invert/Succeed/Cooldown decorators, 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 FieldOfViewDegrees difficulty knob (Perfect 360 → Bad 100) gates npc:canSee(target, maxRange?, fov?) against the rig’s actual facing before any raycast (hearing stays omnidirectional, covering the rear); RequireSight targeting tracks briefly-occluded targets for MemorySeconds then 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 (with BackAway for fighters that give ground) breaks the target’s line of sight from grid cells or tagged nodes; kit:findPeekPoint finds the corner beside a spot with eyes on the threat. Defense: per-archetype Reactions (Against attack types, per-reaction Difficulty, cooldown consumed on ATTEMPT) roll DefenseSkill: kit Hitscan/Melee actions roll the victim’s reactions automatically when it’s an npc, npc:defend(context) exposes the same gauntlet to game code, and Npc.Defense(npc, name, success, context) publishes deflects AND fumbles. Per-action Difficulty overrides 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; explicit Impulse hit flings distribute per part, since with rigid joints cut every limb is its own assembly and impulses on the massless ghost root moved nothing. Default FrictionTorque relaxed 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). New Options.Limits merges 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 the CKRagdoll state channel and ChloeKernel/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 full refresh() stays for map-wide changes).
  • BonePhysics: boneless accessory sway + ambient wind. bindAccessory (run automatically by bindCharacter) 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. MaxSwingDegrees keeps hair drifting instead of flipping. New AmbientWind = Vector3 sim 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 = true joins the default target roster so other npcs hunt that kind through ordinary acquisition (self-targeting excluded, faction-mates filtered); override GetTargets for 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-aware Audio.Cue bus events; speak() queues dialogue on the Voice bus, auto-ducks, and publishes Audio.Subtitle/SubtitleEnded for the game’s UI; reverb zones apply by listener position with priority overlap; soundscapes scatter randomized chirps over an ambient bed. Kernel-only integrations: Alert rings NPCKit ears so player-audible sounds are also NPC intel, Occlusion = "Path" muffles by the pathfinding route around geometry (walls block, corridors carry), bindSettings persists bus volumes through the validated Settings service, and AudioKit.server/audio:listen broadcast 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, Properties passes raw overrides, and release() 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/AnimKit banks via assetIds(), plus extra ids and whole Instances), dedupes, and warms them in batched PreloadAsync calls off-thread, narrating Preload.Started/Progress/Done on 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 through Bone.Transform (the animation slot) composed against the parent’s effective world, never WorldCFrame, 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 on Destroying, handle:destroy() restores Transforms, sim:destroy() releases everything. bindCharacter scans accessories/layered clothing for boned parts; bindParts drives plain part chains (stud tails) with the same solver. Settings per bind: Damping/Stiffness/GravityScale/WindScale; sim options include injectable Wind and ShouldSimulate.
  • 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, Impulse flings, Duration auto-release, and a MaxActive cap 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 publish Npc.Hitscan(npc, origin, hitPosition, victim?) tracers; Action.Gravity runs 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 PathSkill difficulty 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 kit PathMethod still overrides. Every computed route publishes Npc.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 (new HearingMultiplier/HearingBlurStuds knobs on every preset: Perfect pinpoints the exact position, Bad hears a vague 30-stud blur; the ear model itself lives in the standalone Senses module), Sounds.Topics auto-emits from listed bus topics at the acting player’s position ({["Weapon.Fired"] = 60} = gunshots make noise with zero wiring), and Sounds.Occlusion picks 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 in State.HeardPosition/HeardAt/HeardSource for 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. Injected IsWalkable may 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 (Perfect aimbot through Bad, 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: Projectile actions fire the same kernel Projectiles definitions players shoot (npc handle rides as the owner session), Custom actions receive a pre-aimed direction for pointing at existing WeaponKit/SpellKit server logic, Melee gates on range for creature attacks. Your game’s loop drives the handles (kit:all(), npc:updateTarget, npc:act, npc:moveTowards re-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 injected IsWalkable for RTS maps/dungeons/deterministic specs, and searches carry a MaxExpansions budget 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; Match pins an arg (zone name, weapon id); Filter takes over for actor-less topics. AutoAssign, Repeatable (resets fresh on completion), OnComplete rewards, 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.Interact chain (kit gate at 50; game rules prepend) before OnInteract runs. 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_Sync pushes authoritative values back, and SettingsClient gives the client fetch/optimistic-set/onChanged over the same channels (wire to InputDriver rebind for 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 to AnalyticsService: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; optional WatchErrors bridges Kernel.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 flipped FailOpen where a crashing validator would PASS payloads (High), Open = true escape 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-style assert(#findings == 0) gates. HookRegistry gained listPoints(). See Fuzz & audit.
  • Zone-scoped replica interest: replica:bindZone(zones, zoneName). Subscription flips the instant Zone.Entered/Left fires (full snapshot on entry, remove on exit, quantize baselines flushed) instead of waiting for the replica’s interest scan, which stays wired underneath via isInside so 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) and trackTag(tag) (CollectionService, live; untag or Destroy fires the leaves) put entities in the sweep, riding new Zone.EntityEntered/EntityLeft bus topics while Zone.Entered/Left stay players-only. add() now returns a per-zone handle carrying Entered/Left Signals plus OnEnter/OnLeave callback options, so handlers stop name-filtering the global topics; session-end and remove() leaves flow through the same dispatch (handles fire everywhere). One zone can span several parts (add(name, { PartA, PartB }), addPart), and addTagged(tag) builds zones straight from Studio tags: zone name from the ZoneName attribute (fallback: part Name), same-named parts unioning into one zone. New reads: entitiesIn(name), isInside(name, occupant). Injected Query doubles 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.Forgive pardon 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 (Owner option), waitForGeneration surfacing GenerationError, and a public validate() 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 demonstrating params: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, Contacts attributes 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 and Stats.MemoryTrackingEnabled is 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) plus Stats.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.5ms BudgetSeconds floor; a frame-interval guard drops to the floor when frames run >25% late (covers unmeasured costs like replication serialization, and misreporting platforms). Explicit BudgetSeconds keeps the old fixed-slice behavior; TargetFrameSeconds opts 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.Stats records the live budget, heartbeat-phase busy time, and physics time every step, plus a windowed step avg/max/deferred drained by takeStepWindow(); new Diag{Role}FrameBusyMs/PhysicsMs/FrameTargetMs/StepMaxMs attributes; 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.
  • Overload simulation 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, Deferred goes 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 the ChloeKernelSimulation ReplicatedStorage 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 by enableDiagnostics (new Diag{Role}GcAllocKbS/GcCyclesMin/GcReclaimKb attributes), 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-only ChloeKernelSimControl remote, 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}_Backups by default; Memory/Http/custom for external DBs), optional per-save Mirror to a live slot, and load-time fallback that restores the newest readable backup when a record is corrupt or unmigratable (profile.RestoredFromBackup + Kernel.ProfileRestored bus 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.Clock injectable 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 accepts Codec = "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 selectable row 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 new Diag{Role}BudgetMs attribute) and the server memory row reads against Roblox’s 6.25GB process cap; ImGui bars now support hover hints.
  • Replica Quantize deadbands ({[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; the Packages/ 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.
  • NumberVlq wire 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 about buffer.readbits/writebits instead of file-level allowances.
  • BusBridge fulfills the Bus’s advertised NetBridge: ServerTopics (wildcards) auto-forward to every client as ordinary bus publishes, Bus:publishRemote forwards any topic explicitly, and client publishes ride a rate-limited intent through exact-name whitelisting (fail-closed, no wildcards upward) plus the Intent.BusPublish hook 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; declarative Validate rules (Range/OneOf/MaxLength) compile into the fail-closed hook chains at priority 5; Net:onRequest added 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 fresh boot() on the same modules works (tests, in-place re-runs).
  • ChloeKernelDebugUserIds allowlist: comma-separated UserIds restricting the live-server debug surface per user: panel attach, the NET/log mirrors (now per-recipient sends, never FireAllClients), and the SimControl intent.
  • Teardown across the board: ProjectileClient destroy() (+ client-side safety TTL and serial-wrap recycling), ReplicaClient unlisten()/destroy(), ImGui Window:destroy() (drag connection severed), Panel/NetPanel detach handles, Effects detach(), Zones destroy(), ErrorWatch destroy(), 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 to UpdateAsync as 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 of src; leftover Hello.luau scaffolding removed.
  • InputDriver multi-key bindings: Keyboard/Gamepad accept one Enum.KeyCode or an ARRAY of them, so one action can ride several physical inputs; rebind and getBindings snapshots mirror the shape. See InputDriver.
  • Typed public surface for Studio autocomplete: ServerKernel fields carry concrete types (Scheduler/Hooks/Bus/Services/NetDriver instead of any), kernel:net() returns a typed NetDriver (the module loads at boot purely for its type; driver CONSTRUCTION stays lazy), session.Profile is typed as Data.Profile, and registerService/spawnProcess parameters are annotated.
  • Intents/requests are now fail-closed on a MISSING validator as well as on a failing one. A channel with a Handler but zero validators rejects every payload unless explicitly marked { Open = true }, closing the footgun where an unguarded GrantCoins/SellItem intent let a client mint currency or set a stat at will (an absent validator used to mean “accept anything”). Declaring Validate rules counts as a validator; Open = true is the audited escape hatch for genuinely no-auth channels (payload-less toggles, idempotent ready-ups). Surfaced three ways: a one-time [NetDriver] ... fail-closed warn naming the channel, an Unguarded verdict in the NET panel, and Net:auditValidators() for a proactive post-boot sweep. Registry channel defs gained Open too. 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.describeWire is public).
  • Rewind’s MaxFutureSeconds default raised 50ms → 200ms: real GetServerTimeNow skew plus frame timing on rough connections could push honest stamps past 50ms and reject as FutureTimestamp; 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.MemoryTrackingEnabled first (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 (ShowHitboxes attach option / lag:showHitboxes(enabled)): every castRay draws 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.drawHitbox is 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’s ShowHitboxes display. Definitions without Hitbox keep the legacy behavior exactly; in capsule mode OnHit receives { 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 (pure Rewind.rayCapsule underneath, 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 whenever config.Rewind is attached, with per-weapon Hitbox = { 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.
  • 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.create on 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 ClientTopics that also matched a ServerTopics wildcard reflected the server Session object (plus attacker-chosen args) to every client; client-originated publishes never auto-forward down anymore.
  • 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-Match when 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.new asserts LockTtlSeconds >= 2x AutosaveSeconds: the autosave is the lock heartbeat.
  • One in-flight save per profile: autosave, save(), and release() 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 releaseAll bails 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 PurchaseGranted with 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 throwing save() can no longer leak both InFlight locks.
  • findUnserializable rejects 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, and getRange reports a corrupt entry instead of throwing.
  • Process suspend/resume race double-stepped forever: resuming before the queued step drained scheduled a second copy and both chains re-enqueued themselves; a StepQueued flag 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-out invoke resumes with RejectValue instead 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() resumes wait()-parked threads instead of leaking them suspended forever; Pool:release() errors on a double release (two acquires could share one Instance); expect().toEqual survives 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.
  • String/Buffer length 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/3 error on unregistered values both ways instead of writing index 0 and decoding to a silent nil; EnumItem errors clearly on unknown types/values; UDim/UDim2 Scale 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.
  • 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 (CKDead attribute + clear dispatch error) instead of burning a full timeout per dispatch forever, and guards dispatch-after-destroy.
  • 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.
  • 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 FindFirstChild sweep per player per projectile per tick), and the default raycast reuses a single RaycastParams, reassigning FilterDescendantsInstances only when the list identity changes.
  • Process steps stopped allocating a queue entry + handle per resume: new Scheduler:scheduleEntry re-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:publish no 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 (FindFirstChild on spawn/respawn, not SampleHz times a second per player); Zones sweeps build one character include list per sweep with one reused OverlapParams; Logger.emit early-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 as Process <name>, so the panel now reads Process SimBolt 19.8ms/s instead of lumping everything into Process:89, which also revealed the real cost driver in the sim (bolt Part.CFrame writes, 3× the cost of 200 pure-math entities).
  • 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: TasksDeferred was 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 and Kernel.Overload streak moved to Kernel priority: health reporting must survive the very overload that starves Background work.
  • Server step diagnostics no longer phase-lock: Diag{Role}StepMs published 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 new StepMaxMs.
  • Vendored Packet (marked mod #5): the server flush loop is pcall-isolated. A FireClient that 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.Data was 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.cancel their 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 Any engine’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; Vector3F24 components 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.
  • 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).

ChloeKernel v0.9.1. Access to the framework is private and granted individually by Chloe. Games built with it credit CryptedChloes in their description.