Changelog
Tagged versions ship a built .rbxm on the GitHub Releases page — insert it into a place to pin an exact framework version instead of syncing main. Pushing a v* tag builds the artifact and cuts the release from the matching changelog section automatically.
0.7.2 — 2026-07-11
Section titled “0.7.2 — 2026-07-11”- SpatialKit: spatial queries as sentences —
Kit:sphere(center, 15):players():alive():collect()replaces the per-call-site GetPartBoundsInRadius + character walk + team check: shapes (sphere, orientedbox(cframe, size), true-3Dcone(origin, direction, range, fovDegrees),capsule(from, to, radius)) chain population selectors (players(),npcs()= the NPCKit roster,tagged(tag); none = players plus npcs) and filters (alive(),team(name)via TeamKit so npc Factions count,hasTag,where(fn),exclude(caster)) into terminals (collect/first/count/nearest(n)sorted by distance). Positions read live at query time; a Spatial character index broad-phases the players population when given; every shape test is pure and exported. CombatKit-style abilities, AI scans, loot radii, and objectives all build on one query. See SpatialKit - Curve: piecewise scaling curves —
Curve.new():add(1, 100):add(5, 350):add(20, 1200)holds sorted keys and answersat(x)(Linear/Smooth/Step ease per segment; Clamp or Linear extrapolation outside the keys) andsolve(y)(first-crossing linear inversion — “what level reaches 800 xp”); re-adding an x replaces its y,Curve.from(pairs)bulk-builds, keys binary-search. XP tables, damage falloff, prices, loot weights — one primitive instead of if-ladders. See Curve - Stages: gameplay resource streaming — zone-occupancy lifecycle (nothing to do with Roblox instance streaming): a stage binds resources to a Zones name, the FIRST player in runs
Load(returns the unload closure), the LAST player out unloads afterLinger(default 10s, so stepping out and back never thrashes spawns),OnEnter/OnLeavefire per occupant, andReplicashandles autobindZonewhile loaded. Occupancy rides busZone.Entered/Left(leavers count down automatically), a Zones instance at attach seeds players already standing inside at define time, and every closure failure warns without breaking the sweep. BusStage.Loaded/Unloaded/Entered/Left— bridge them for client cosmetics. See Stages
0.7.1 — 2026-07-11
Section titled “0.7.1 — 2026-07-11”- Rng:
float(max)one-arg form —float(5)now means[0, 5); previously a lone argument defaulted max to 1, sofloat(5)degenerated toNextNumber(5, 1). Thefork()seed derivation comment no longer claims textbook FNV-1a (it is an FNV-flavored fold that truncates to stay inside Luau’s exact-integer double range; the contract is determinism per (seed, name), unchanged). See Rng - AchievementKit: pre-profile progress merges on
Kernel.ProfileLoaded— events landing before the profile attaches counted into session data and were then silently dropped; they now merge with QuestKit’s rule (unlocks carry over, the higher count per id wins, counts for already-unlocked achievements drop). See AchievementKit - Hazards: destroyed anchors actually retire — a
Destroy()’d BasePart/Model keeps answeringPositionwith its last value, so the liveness check never fired; anchor resolution now treats a nilParentas dead and the next sweep retires the hazard (spec now exercises a real Instance, not just a duck double). See Hazards - Hazards: serial wraparound skips still-live serials — a wrapped counter could reassign a live hazard’s serial, orphaning it without ever firing
Retired; allocation now skips live serials (same allocator stance as Projectiles) and returns nil only if all 65535 are in flight. See Hazards - CameraKit: instant release restores the pose —
release()with zero/nil seconds restored CameraType and FOV but left the camera CFrame wherever the last shot pointed; it now lands back at the captured pose (Custom cameras re-solve from the character next frame regardless). See CameraKit - Chronicle: double attach fails loud — attaching a second recorder to the same kernel stacked wrappers (double-recording, with detach unwinding only the outer one); attach now errors until the existing recorder detaches. See Chronicle
- Impact:
DuckBusdefaults to a real bus — the default was"World", which is not one of AudioKit’s default buses (Music/SFX/Ambient/Voice/UI), sopulse()with defaults errored on the first duck against a default-configured AudioKit; the default is now"SFX". The spring comment no longer claims “critically-damped-ish” — the live constants are genuinely underdamped (damping ratio near 0.5), matching the ringing the spec asserts. See Impact - VfxSuite + CutsceneKit: registration caps at 255, not 257 — the cap checked before incrementing, so a 256th registration succeeded with wire id 256 and only errored at play time (the id rides
NumberU8); both registries now fail at register with the corrected “at most 255” contract. See VfxSuite, CutsceneKit - Gait: the root offset no longer compounds — the height/tilt offset composed onto
RootJoint.Transform’s live value, which is the kit’s own previous write whenever no animation drives the joint, accumulating drift over a session; the stepper now tracks its last written value (post-write readback) and strips it before composing, so the offset applies exactly once whether or not an Animator rewrites the slot. See Gait - Gait:
PlantSmoothingactually smooths — the plant/unplantsetWeightcalls omitted the fade argument, snapping IK weights instantly; they now passPlantSmoothing, and the stepping path (plants, fades, root offset) gained spec coverage. See Gait - Rules: the gate order is documented —
once -> cooldown -> countregardless of chain order;once()means a single fire per key ever, so combining it withcount/cooldownstill fires at most once (for repeated-but-throttled awards usecooldownalone). Behavior unchanged; the module header and cookbook now say so. See Rules - CutsceneKit:
Linepublishes the raw string — the header no longer implies the kit calls Text; localization happens in yourCutscene.Linesubscriber (send keys, localize at render). See CutsceneKit
Removed
Section titled “Removed”- HEAT debug window — the scheduler heatmap panel is gone; the CLIENT window’s hot-task list and budget bars remain the scheduler view, and the Panel’s profiler sampling reverts to its pre-HEAT form. The HOOKS window is unchanged. See Debug Panels
0.7.0 — 2026-07-11
Section titled “0.7.0 — 2026-07-11”- MaterialKit: real-geometry surface dissolution — where Dissolve flies voxel dots, MaterialKit erodes the actual rendered mesh: faces vanish and return in Perlin-noise rank order (optional directional
Bias), verified live — the baked MeshPart renders from the live EditableMesh object, so everyRemoveFace/AddTriangleshows immediately with no re-bake.bind(meshPart)opens the part’s own mesh asset (same two permission gates as HairKit, denials return(nil, reason));bindBox(part, {Subdivisions})needs NO asset permission — a generated subdivided box overlay hides the original (transparency restored on destroy) while collision stays on the game’s part.setProgress(alpha)erodes AND knits (a receding driver restructures the surface — removed triangles re-add against their recorded vertex ids),play({Duration, Reverse?})sweeps on one Heartbeat stepper,OnFaceGone(centroid, normal)per removal for dust hooks, pureMaterialKit.orderranks. Local-only VFX, same broadcast stance as Dissolve. See MaterialKit - CameraKit: cinematic camera control as data — shots declare what the camera does (
Static,Followwith exp-smoothed offset,Orbit,Rail= Catmull-Rom through CFrame points with eased progress and optionalLookAt), one PreRender stepper evaluates the active shot against LIVE targets each frame,blend(shot, seconds, ease?)eases from the real current pose so moving targets stay tracked through transitions,cutsnaps,release(seconds?)blends home and restores the CameraType; a dead target hands the camera back instead of freezing. Targets: Vector3, BasePart, Model, or function; per-shot FOV; pureCameraKit.ease/rail. See CameraKit - CutsceneKit: scenes as timelines — flat
{Time, Action, ...}event arrays shape-checked at register (Shot/Blend = CameraKit, Anim = AnimKit rigs resolved from a namedRigsroster so shared definitions never hold Instances, Sound = AudioKit at the scene origin, Vfx = VfxSuite, Line = busCutscene.Line(text, seconds)for Text-key subtitles, customHandlers); the server broadcasts ONECKCUT_Playpacket (U8 id + origin CFrame) and every client plays the whole scene locally;skip()jumps to the end — pending events drop, scene-started anims stop, the camera releases with a blend. BusCutscene.Started/Ended/Skipped/Line. See CutsceneKit - DialogueKit: server-authoritative conversation trees — nodes with Text-key lines,
Choiceswith per-playerWheregates (hidden choices cannot be picked — indices mean the VISIBLE list, and gates re-check at pick) and server-sideRunconsequences,Nextchains,OnEnternode actions; every reference validates at attach. Wire:DLG_Syncstate push per node ({} on close), fail-closedDLG_Choose/DLG_Advanceintents gated against the session’s live node;begin()stays server-side (wire it to an InteractionKit prompt), leavers clean up without intents. HookDialogue.CanBegin(fail-open); busDialogue.Started/Node/Choice/Ended(reason). See DialogueKit - HOOKS debug window — every hook point live in the F8 panel: fire and rejection counts, per-point chain cost (avg/max ms measured around the live call), fires/s sparkline, fail stance and handler count from
listPoints()(defined-but-never-fired gates show dimmed), pause/reset; decoratesHooks.fireand restores on detach. See Debug Panels - HEAT debug window — scheduler heatmap in the F8 panel: whole-scheduler step ms/s strip, color-graded per-task bars (green under 1 ms/s, amber under 5, red past it) over the hot-task profiler, per-task rolling history sparkline on click, deferred-task counter; the Panel feeds it samples so the two never fight over the shared profiler window. See Debug Panels
- Rng: deterministic seeded randomness —
Rng.new(seed)wraps Roblox’sRandomas a first-class object:float(min?, max?),int(min, max)(inclusive),choice(list),shuffle(list)(Fisher-Yates over a copy, input untouched), andfork(name)deriving an independent child stream from (seed, name) via FNV-1a — consuming an extra chest roll never shifts the NPC decisions; same seed, same sequence, every machine. See Rng - Time: one clock surface —
Time.attach(kernel)setskernel.Time:now()(os.clock),server()(GetServerTimeNow, the claim-stamp clock),frame()(accumulated frame seconds, never pauses), andscaled()(game time honoringsetScaleandpause/resume— slow motion and pause become one switch instead of a hunt through every timer; the accumulator integrates scale×dt, never rewinds). Injectablestep(dt)spec seam; busTime.ScaleChanged/Paused/Resumed. See Time - Profile snapshots —
profile:snapshot(label?) → id,:snapshots(),:rollback(id): in-memory point-in-time deep copies ofDatafor admin tooling and risky-mutation guards (take one before a trade, roll back on abort); the ring keeps the newest 8, snapshots survive repeated rollbacks, session-only and never persisted — persisting a rollback is the caller’ssave(). See DataDriver - Rules: bus automation engine — declarative event automation without spaghetti subscriptions:
Engine:on(topic)chains:where(predicate)(arg filter),:count(n)(every nth surviving match),:once(),:cooldown(seconds), and:key(fn)(counting identity, default the first event arg — the session in every kit’s convention), then:run(action)arms it and returns{disconnect, Stats = {Matched, Fired}}; counters hold weak keys so departed sessions drop, actions run throughtask.spawnso a throwing rule never breaks the bus. See Rules - AchievementKit: persistent conditions over the Bus — an achievement is a topic, an optional
Wherefilter, and a targetCount; progress lives inprofile.Data(Field default"Achievements") so it survives rejoins, unlocks fire once per player ever,Rewardruns at unlock, andprogress(session, id)/unlocked(session, id)read out for UI; busAchievement.Progress(session, id, count, target)andAchievement.Unlocked(session, id). See AchievementKit - Chronicle: kernel flight recorder —
Chronicle.attach(kernel)decorates the liveBus.publishandHooks.fire(restored ondetach()) and records every event and hook verdict into a ring:trace("Inventory")returns the last 30 seconds of entries whose topic or args mention it,trace(fn)filters by predicate,format()prints the timeline — “why did this player lose their sword?” becomes a lookup instead of a guess; summarization keeps names and numbers, never object references, so the recorder cannot pin sessions. See Chronicle - Text: localization over locale tables —
Text.attach(kernel, {Locales, Default?})resolvesget(player, key)through the player’sLocaleId, its language half (fr-fr→fr), then the default; a key missing everywhere warns once and returns the key so untranslated UI reads as its key instead of erroring;format(player, key, vars)interpolates{Name}placeholders (unmatched stay literal), pureText.interpolatefor tests; send KEYS over the wire and localize at render. See Text - Sweeps: phantom hitboxes for big rigs — server-authoritative attack volumes authored as root-local capsule tracks (pure functions of swing phase), reconstructed in world from the live root CFrame each tick and tested mathematically against player capsules with sub-stepping between phases, so a one-tick tail snap cannot skip a target; per-swing victim dedupe, Spatial broad phase, zero dependence on physics parts or replication lag. Bus
Sweep.Hit(swing, victim, position); damage stays game-side. See Sweeps - VfxSuite: declarative timed VFX pipeline — multi-stage effects author as flat
{Time, Action, ...}event arrays shape-checked at register (Spawn = pooled template withLifetimeauto-release, Emit = particle bursts on named emitters, Sound = AudioKitplayAt, Dissolve = a Dissolve play on the spawn, anything else = customHandlers); the server sends ONECKVFX_Playpacket (U8 id + CFrame) and every client drives the entire lifecycle locally in one Heartbeat stepper; sequences register on both machines in one shared module. See VfxSuite - Impact: screen-space feedback coordinator —
pulse(origin, options)localizes intensity by camera distance (quadratic falloff), kicks a damped camera spring (pulses stack), side-chain ducks an AudioKit bus with auto-release, and dips blur/color correction only when DeviceBench quality clearsPostQuality— low-end devices keep the shake and duck, never the post-processing cost; pureintensityAt/springStep. See Impact - Gait: procedural foot planting and body attitude — each leg down-casts from a look-ahead point (
effector + velocity * LookAhead) and drives an AnimKit Reach IK onto the hit (airborne legs fade back to the animation), the root joint Transform carries a smoothed body-height offset over uneven footing plus a velocity-derived bank/pitch tilt (the dragon leans into the pivot before the track shifts); all client cosmetic through Motor6D Transform slots and IK targets, one PreRender stepper for every bound gait; puretiltFor/heightOffset. See Gait
0.6.3 — 2026-07-10
Section titled “0.6.3 — 2026-07-10”- Projectiles: authoritative curved paths — a per-definition
Path(seed, age, origin, velocity, flightSeconds?) → Vector3evaluated identically on both machines: the server sweeps each tick’sPath(t-dt) → Path(t)segment for walls and capsule hits (the hitbox follows the curve exactly, no more straight hit line under an arcing visual), clients render the same function and bank along its tangent. Deterministic from spawn-packet data (serial seed, origin, velocity, flight seconds) — zero extra wire bytes; keep the function in one shared module for both defs.PathOffsetremains cosmetic-only for flourish that must not move hits. See Projectiles - Hazards: dynamic procedural zones with a client mirror — annulus bands (inner/outer radius + height) evaluated as distance checks in a cadenced sweep, no phantom parts: radii as functions of age (expanding fire rings), anchors as fixed points or moving Instances (the band follows the caster and retires if the anchor dies),
Durationauto-retire with everyone inside receiving Left before Retired, and occupancy tests each occupant’s last-position→position SEGMENT against the band so a sprint through a thin ring cannot tunnel between samples (a through-crossing fires Entered then Left in the same tick). Wire:CKHZ_Spawn(serial, defId, origin, seed, duration, anchor?)+CKHZ_Retire(serial);HazardClientrenders pooled, seed-deterministic ring VFX from the same shared radius curves, following moving anchors, safety TTL past duration. Damage stays game-side: apply an Effect onHazard.Entered, cleanse onHazard.Left. BusHazard.Spawned/Entered/Left/Retired. See Hazards - ProjectileClient:
OnImpactcarries the wire serial (ported up from Withcraft&Wizardry) —OnImpact(position, velocity, serial): the serial is the same seedPathOffsetreceives, so impact cosmetics reproduce the exact per-shot roll the visual flew with; extra arg, old callbacks ignore it. See Projectiles
Changed
Section titled “Changed”- ProjectileClient: facing comes from authoritative velocity (ported up from Withcraft&Wizardry) — only curved rigs (
Path/PathOffset) bank along their rendered motion; everything else orients on the true heading, so the muzzle-blend convergence no longer rolls and pitches wide visuals (blade slashes) for the whole flight of a short-range shot. See Projectiles
0.6.2 — 2026-07-10
Section titled “0.6.2 — 2026-07-10”- ProjectileClient: the cosmetic stack (ported up from Withcraft&Wizardry) —
PathOffset(seed, age, velocity, flightSeconds?)per-frame curve hook,OnRender(visual, seed, age, flightSeconds?)per-frame visual hook,CosmeticOrigin(caster, origin)latency masking (the visual spawns at the muzzle THIS client currently sees and converges onto the true path, capped at 25 studs),OnImpactreceives the impact velocity as a second argument (orient VFX to the surface the shot died on), and shots with known flight seconds park at their landing point instead of overshooting while the hit/expire packet is in flight. See Projectiles - Projectiles: per-shot flight control — the spawn packet carries flight seconds and the caster (cosmetic envelopes and latency masking read them),
fire()takes optionalmaxDistance(point-targeted expiry: lifetime caps atmaxDistance/speed) andspeedMultiplier(per-shot speed with no wire change),OnExpirereceives the expiry position and the expire packet carries it to clients. See Projectiles - Movement:
AntiExploit.Exemptstanding exemption —kernel.Bus:publish("AntiExploit.Exempt", player, true|false)for server-sanctioned free movers (broom riders, vehicles, scripted knockback); unlike repeated Forgive it never resets the Rewind buffer, so lag compensation keeps validating the rider’s shots. See Movement Monitor - Packet: paged name-to-id mappings + early-packet parking — channel ids ride paged
Configurationchildren instead of RemoteEvent attributes (Roblox caps an instance’s total attribute payload at 1024 bytes; a real game’s channel count blew past it and errored channel definition), and payloads arriving before their id page park and replay when the map lands instead of erroring the receive loop. See Packet & Wire Types - NET window: whole-game traffic row — “Game total” shows this machine’s full network in/out KB/s (engine replication + physics + every remote) from the Stats service next to the kernel-channel Wire row. See Debug Panels
Changed
Section titled “Changed”- Diagnostics: per-role child containers — kernel diagnostics write short attribute names on
ChloeKernelDiag<Role>Configuration children instead of ~29 long-prefixed attributes on ReplicatedStorage itself (the 1024-byte attribute cap errored the diag task in real games); hot tasks ride three 50-char-capped attributes; the debug panel translates legacy keys - Debug panels boot hidden — the F8 panel and SIM control start with their ScreenGuis disabled; F8 toggles them in. See Debug Panels
- NetTap: Instance sized at zero bytes — instance refs ride the remote’s engine side array, not the kernel buffer; refusing to size them zeroed the wire counter for any Instance-bearing channel
0.6.1 — 2026-07-10
Section titled “0.6.1 — 2026-07-10”- WizardDuel template: casts complete — all three spells now declare
ChannelSecondsand SpellKit defaults a missing value to 0 (instant) instead of erroring inbeginCast; WizardDuel and GunArena clients call the intent handle’sfire(...)(the lowercase dot-call NetClient actually returns) instead of a nil:Firemethod. See SpellKit, Starter Templates - Rewind: the claimed fire direction now gates hits —
validateShotrejects claims whose origin-to-hit vector strays pastDirectionDotMin(default 0.94, about 20 degrees) fromShotClaim.Directionwith reasonDirectionMismatch; previously the field was carried but never read. See Rewind - Studio boot gate: spec failures halt the play test —
Main.serverasserts zero failed specs from both suites, matching the audit and fuzz gates instead of warning past red tests. See TestKit - LiveConfig: TTL expiry no longer splits the fleet — a poll that finds the document gone (MemoryStore TTL) reverts every override so long-lived servers agree with fresh ones booting into defaults; unreachable MemoryStore still holds current values. See LiveConfig
- Analytics: playerless drops are counted and WatchErrors warns — the default
LogCustomEventdestination counts skipped playerless entries inStats.DroppedPlayerless, and attaching withWatchErrorsplus the default destination warns that every ScriptError event would be dropped (they are playerless by construction). See Analytics - Settings: rejected client writes roll back — the kit’s validator pushes the authoritative value over
Settings_Syncon rejection (unknown key or invalid value), so SettingsClient’s optimistic cache self-corrects instead of keeping the rejected value. See Player Settings - ShopKit: stock caps survive rejoins — per-window buy counters live in the profile when one exists (rotating windows persist until they roll, static-shop caps become per-player lifetime), stale window counters prune on the next buy. See ShopKit
- InventoryKit:
Intents = falseand a MaxSlots wire guard — a second attach on the same kernel can skip INV_* channel wiring instead of crashing on redefinition, andMaxSlotsasserts 1..255 at attach (slot indices rideNumberU8; larger bags would have client-unreachable slots). See InventoryKit - CompanionKit: leavers never orphan companions — session-leave cleanup moved out of the intent wiring, so
Intents = falsekits destroy a leaver’s companion too. See CompanionKit
Changed
Section titled “Changed”- README truth pass — removed the nonexistent
Npc.Idle/Npc.IdleEndedtopics,Npc.Tacticdocuments its three real roles, channel integer coercion documented as wrapping (replica fields clamp),Boolean1marked channel-only (replica schemas useBoolean8),BonePhysics.attach()in every example, receipt ledger default corrected to 2000,rebindexamples carry the device argument, Bad hearing blur corrected to 32 studs
0.6.0 — 2026-07-09
Section titled “0.6.0 — 2026-07-09”- ActorPool: fork-join over the worker pool —
pool:forkJoin(items, {ChunkSize?, Context?})splits a batch into chunks (default 4 waves per actor) and every actor pulls the next chunk the moment it finishes its last, so uneven items self-balance across the pool; results stitch back in item order and the calling coroutine resumes once with(true, results)or(false, firstError). The worker fn runs asfn(item, context)per item; a chunk failure carries the absolute item index, stops new sends, and drains outstanding chunks. Compose phases by forking again over the joined results.dispatch()is unchanged. See ActorPool - TablePool: scratch table recycling —
acquire()/release(t)recycle cleared Lua tables with their capacity intact (double release fails loud, idle stack caps at 256); Spatial grew allocation-freeradiusInto/boxInto/coneIntovariants that append into caller scratch, and the hot sweeps adopted them: NPCKit target-scan prefilters and Projectiles per-projectile candidate sets ride pooled scratch, NetGovernor per-player stats tables reuse in place. See TablePool - Mounts: map-authored attachment points — tag any instance
CKMount, set aMountTypeattribute plus per-type attributes, andMounts.attach(kernel, options)wires the systems at level load:Soundmounts become AudioKitplayAtemitters (SoundName,Volume?,Speed?),Zonemounts feedZones:addPart(ZoneName),BoneChainmounts bind BonePhysics chains (Damping?,Stiffness?,GravityScale?,WindScale?); built-ins mount only when their system is passed, so server and client attach off the same tagged map.Handlersextend and override with custom types returning cleanup closures; tag add/remove signals mount and unmount live (StreamingEnabled maps mount what streams in); handler failures warn and stay contained. BusMount.Added/Mount.Removed. See Mounts - Spatial: flat spatial-hash index — pointer-free grid over ids at world positions (packed integer cell keys, exact-filtered queries, coordinates clamp at ±524k studs, same-cell moves are position writes):
radius/box/cone(horizontal FOV dot) /nearest(expanding rings), plusSpatial.characters(kernel)for a self-refreshing player-root index. Wired consumers:Projectiles.attach { Spatial }replaces the per-tick full-roster scan with a per-projectile radius query (fresh roots for the selected candidates keep hit math exact),NPCKit.attach { Spatial }refreshes the index inupdate()and broad-phases target scans through a stale-padded radius before any sight test. See Spatial - NetGovernor: adaptive per-client replication — grades every player Good/Strained/Poor from ping median + jitter (mean absolute deviation over a rolling window), unreliable packet loss (sequence-numbered echo probes over
NG_Probe/NG_Ack, held until the client’sNG_Readyhandshake so join silence never reads as loss), and the DeviceBench quality hint (NG_Device, clamped, floors at Strained, never Poor by itself); the worst axis wins. ReplicaService sends deltas to Strained clients every 2nd tick and Poor every 4th with per-player phase stagger — skipped fields bank per subscriber and merge into the next on-cadence send, so discrete state never goes missing and snapshots/removals always send; unreliable state channels grew{ Priority = "Low"|"High" }and Low traffic sheds to Poor links before the wire (probe channel is High: shedding probes would lock the tier down). Shed counts inGovernor.Stats; busNet.TierChanged(player, tier, stats); client halfGovernorClient.attach(). See NetGovernor - Forensics: anti-exploit flight recorder — every player’s kinematics roll through a ring (
[t, position, look, velocity, humanoidState]atRecordHz10 forWindowSeconds20); a watched bus topic (defaultAntiExploit.MovementViolation) freezes the window into a capture and recordsTailSecondsmore; re-flags during the tail append reasons to the same capture and leavers finalize immediately. Delivery: function destination or{ Url }JSON POST (never blocks the sweep, failures fall into the in-memory ring, lastMaxCapturesreadable viacaptures());export()emits the JSON a web tool can rebuild the run from;Forensics.replay(capture)walks an interpolated ghost rig through the movement in Studio. Captures hold movement, reasons, and the user id only. BusForensics.Captured. See Forensics - HairKit: automatic hair rigging over EditableMesh — boneless catalog hair gains a generated skeleton at runtime:
plan()partitions mesh vertices into radial sectors x depth bands and emits a bone chain per sector (default 10 bones) with every vertex weighted to at mostMaxInfluencesbones (default 2, clamped to 4, weights normalized; root-plane vertices bind the root and never pull band centroids);rig()injects the skeleton (AddBonewithParentId/Virtual,SetVertexBones,SetVertexBoneWeights), registers the content into the DataModel (CreateDataModelContentAsync(Content.fromObject(mesh))), bakes throughCreateMeshPartAsync, swaps the hardware-skinned result into the MeshPart in place, and creates the Bone instance skeleton the mesh links to by name (write-once, read-many — the baked editable stays rooted for the part lifetime and is never mutated again). Sway rides BonePhysics (its single stepper, camera-distance culling, DeviceBenchMaxChainsbudget).attach()= client bulk manager (no per-accessory scripts);HairKit.server()rigs server-side so the baked bones replicate to every client once — zero per-frame network traffic in either mode. Two permission gates verified live: the “Allow Mesh & Image APIs” experience setting (supported()probes it with a realAddBonecall) and per-asset read permission (CreateEditableMeshAsyncloads experience-owned or creator-owned meshes only; third-party catalog hair returns(false, reason)); either failure keeps thebindAccessorypendulum fallback. BusHair.Rigged. See HairKit - Dissolve: vertex-sampled voxelizer with noise-advected dissolve, restructure, and particle morphs — MeshParts sample EditableMesh vertex positions (surface-shell fallback with Ball/Cylinder silhouette filtering — interior cells drop so nothing dissolves as a filled box; permission denials cached per asset so avatar effects warn once per session), home positions ride the target’s anchor part so reassembly, restructure, and morph endpoints follow moving targets (a player who walks off mid-effect reforms where they are), points snap to a
DotSizevoxel grid with per-cell dedup, and dots advect through a 3D Perlin field (ScatterSpeed,NoiseScale,Drift) while fading;Reverseassembles instead;Duration = nilreturns a manual controller whosesetProgress(0..1)erodes the surface noise-thresholded per dot, and dots above the threshold ease back to their surface cell and resolidify (ReturnSpeed) so a receding driver restructures the target (proximity wards that knit shut).morph(source, destination)voxelizes source and flies every dot into destination’s sampled shape — bottom-up pairing (pairPoints, every destination cell used), staggered launches (Stagger), Perlin turbulence arcs (Turbulence), source-to-destination color lerp — then reveals destination and keeps source hidden; avatars are first-class targets (Decals and Textures hide with their parts): disintegrations, character-to-statue morphs, reforming sources viaReverseplays; chained effects restore the pre-effect transparency, never a mid-chain hidden value.Chunks = true(play and morph) clones every source part as a full-detail flying chunk — meshes, textures, faces, and SurfaceAppearance survive, joints/welds/sounds/emitters strip — riding the same threshold, advection, or morph flight while shrinking into the dust, which keeps original avatar detail where denied-mesh grid sampling would box it; chunks destroy on finish. One Heartbeat stepper across every active effect, pooled dots positioned throughworkspace:BulkMoveTo,MaxPointsscaling 500..5000 by DeviceBench quality when unconfigured,CustomDottemplates,Spin. Local-only VFX; broadcast the trigger for shared playback. See Dissolve - Starter templates — the repo’s
templates/directory ships three complete minimal games as reference code (GunArena: WeaponKit + Rewind + TeamKit + RoundKit + Leaderstats + global boards; WizardDuel: SpellKit + MeleeKit + MoveKit + curse/ward Effects; Obby: CheckpointKit + air steps + kill-brick Zones + ascending speedrun board). One server module and one client module each; linted in CI alongsidesrc. See Starter templates - Studio boot gates in Main.server — every Studio play test now asserts zero High-severity
securityAuditfindings after start and runsFuzz.runagainst every registered channel when the first session joins; handler errors fail the play test loudly. See Fuzz & audit - StreamingEnabled stance documented — from a module audit: server-authoritative systems are unaffected; client-side rules for BonePhysics binds and long-lived model references. See StreamingEnabled
Changed
Section titled “Changed”- API consistency: kernel-integrating modules use attach() —
InputDriver.attach(kernel)andMoveKit.attach(kernel, options)are the canonical constructors;.newremains on both as a deprecated alias. Plain object constructors (Pool.new,NetClient.new) and service definitions (WeaponKit.service) keep their shapes: the rule is attach = wires into a kernel, new = plain object, service = definition forregisterService
0.5.0 — 2026-07-08
Section titled “0.5.0 — 2026-07-08”- PartyKit: same-server player parties — invites with TTL expiry (sweep-cleaned), one party per player (accepting leaves the current party), lazy creation (the party forms on the first accept with the inviter as leader), leader kick/promote, leader inheritance on leave (longest-standing member), disband at zero members, session-end cleanup of membership and pending invites both ways. Six fail-closed intents (
PT_Invite/Accept/Decline/Leave/Kick/Promote, UserIds as F64 on the wire),PT_Syncroster pushes andPT_Invitednotifications,Party.CanInvitehook (fail-open). Parties never touch the combat gates;samePartyandmembers(group-queue feed for Matchmaking) are the integration surface. BusParty.Created/Disbanded/Joined/Left/Kicked/Invited/Promoted. See PartyKit - Effects is now a full status system —
TickSeconds+OnTickperiodic ticks (poison, regen) with catch-up bounded to 8 per sweep;Categorydispel groups withcleanse(session, category?)(no category = every categorized effect; uncategorized effects never cleanse);Immune = {categories}on an effect blocks covered applies while it holds (wards are effects;applyreturns 0 and publishesEffects.Blocked); owners receive aCKEffectsstate push after every change ({[name] = {Stacks, Remaining?}}) for status-icon UI. Bus addsEffects.Blocked(player, name, byEffect)andEffects.Cleansed(player, category?, count). See Effects - LootKit: weighted loot tables with persisted pity — entries yield items (
Countfixed or{min, max}), nested tables (depth capped at 8, references validated at attach), or nothing;PityAfter = Nforce-hits on the Nth consecutive miss with counters persisted per player in profileData.LootPity(rollFor/awardadvance pity, plainrollnever does);award(session, table)grants through InventoryKit and returns(granted, overflow). BusLoot.Rolled/Awarded. See LootKit - CraftingKit: recipes over InventoryKit — inputs check atomically before any take;
Seconds > 0queues one timed craft per session withcancel()refunds;Stationrecipes gate on the caller’s claimed station with proximity verified in the fail-openCraft.CanCrafthook; output that does not fit publishesCraft.Overflow; fail-closed intentsCR_Craft {recipeId, station}/CR_Cancel. BusCraft.Started/Completed/Cancelled/Overflow. See CraftingKit - ShopKit: soft-currency shops over InventoryKit — currency is an adapter (
{Get, Take, Give}; default reads a profile field,CurrencyField = "Coins"); rotating stock derives fromSeed + floor(clock / Every)so every server lists identical stock with zero coordination;Stock = Ncaps buys per player per rotation window; buys grant first and charge for what fit (partial grant = partial price, full inventory = no charge); fail-closed intentsSH_Buy/SH_Sellwith counts capped at 99. BusShop.Bought/Sold/Rejected. See ShopKit - CompanionKit: owned NPC followers — familiars/pets from NPCKit archetypes: one per session (re-summon replaces), a follow loop that walks at
FollowDistanceand pivots beside the owner pastTeleportDistance, death cleanup withCompanion.Died, andAuraholding an Effects buff on the owner while summoned; ownership gates in the fail-openCompanion.CanSummonhook; fail-closed intentsCP_Summon/CP_Dismiss. BusCompanion.Summoned/Dismissed/Died. See CompanionKit - Fuzz: hostile-payload sweep over every registered channel — the runtime companion to
securityAudit(): per-schema-type hostile pools (empty/10KB strings, format specifiers, control bytes, 0, -1, type maxima, NaN, infinities, NaN vector components, nil/nested tables for Any) drive each intent and request through the real hook chain + handler pipeline; payloads that pass validation and crash the handler collect inReport.HandlerErrors(assert it empty as a boot gate); validator throws count as rejects; deterministic perSeed;Driverinjectable for specs. See Fuzz & audit
0.4.0 — 2026-07-08
Section titled “0.4.0 — 2026-07-08”- InventoryKit: the item model — definition registry + slotted per-player bags: stacking (grants top up piles before opening slots), equip slots with displacement and
OnEquip/OnUnequip,OnUseconsumables (return true to burn one; draining a stack auto-unequips),movedrag-and-drop (swap or merge, equip references follow the item). State persists through the session profile (Persist = falsefor session-only battle-royale bags). Grants/takes are server-API-only — no client message mints items; clients ride four fail-closed intents (INV_Move/Equip/Unequip/Use) validated against the server’s books, and the owner gets anINV_Syncstate push after every mutation so inventory UI is pure rendering. HooksInventory.CanEquip/CanUse(fail-open); BusInventory.Granted/Taken/Equipped/Unequipped/Used/Changed. See InventoryKit - Leaderboards: global top-N on OrderedDataStores — queued submits flush on a cadence (newest value per key per window = one write no matter how fast scores change),
KeepBestboards only overwrite improvements through atomicUpdateAsync(Ascending flips the comparison for speedruns), failed writes stay queued and retry next window, andtop()serves a cached page (oneGetSortedAsyncperCacheSecondsregardless of readers; failed refreshes keep serving the previous page). The backend is a first-class seam — Roblox OrderedDataStores by default, custom stores as equals: implement declarativesubmit(board, key, value, keepBest, ascending)(the keep-best rule arrives as data, so an external DB applies it atomically its own way) or DataStore-shapedset(board, key, updater), plussorted; contract asserted at attach.WritesPerFlush(default 30) caps each window so provider budgets (ODS limits, HTTP rate caps) survive submit spikes — overflow keeps its queue spot. NaN/inf submissions refuse; BusLeaderboard.Updated(name, entries). See Leaderboards - TeamKit: teams wired into the combat gates — capacity-weighted auto-balance on session start,
assign/teamOf/sameTeam/players, optional Roblox Teams mirroring for player-list colors, and map-authored spawn ownership (tag partsTeamSpawn+Teamattribute; characters pivot to an owned spawn with the anti-exploit pardoned). Friendly fire off by default, enforced where damage happens: vetoes registered onWeapon.CanDamage(new fail-open hook in WeaponKit’s damage path) andMelee.CanHit; npc handles resolve throughArchetype.Faction, so faction NPCs and same-named teams read as teammates. BusTeam.Assigned(player, team, previous?). See TeamKit - WeaponKit:
Weapon.CanDamagehook point — fail-open gate fired before a validated shot deals damage ({Session, Attacker, Victim, WeaponId, Weapon}); TeamKit plugs friendly-fire policy in here, games add safe zones and duel rules without forking the kit. See WeaponKit
Changed
Section titled “Changed”- Repo-wide comment rewrite — every comment states contracts, constraints, units, defaults, and data shapes; rhetorical framing removed; option comments normalized to
-- <fact>. Default <value>; ~680 comments across 118 files with the code proven byte-identical by a comment-stripping diff; three stale factual claims corrected against the code (Net pipeline order, ServiceManager cycle behavior, AnimKit IK helper class)
0.3.0 — 2026-07-08
Section titled “0.3.0 — 2026-07-08”- MeleeKit: server-authoritative hand-to-hand with parries — attacks are windup / hit-frame / recovery timelines on the SERVER clock; combos cancel recovery along declared
ComboNextchains (Melee.Combocounts the string); blocking chips (BlockScale), guard-breaks shatter blocks (full damage, guard drops), and timed parries beat both: a parry press arms aParryWindow(+LatencySlackso wire travel doesn’t eat honest frames) that kills the incoming swing and staggers the attacker (StaggerSeconds, x1.5 for parried guard-breaks — the triangle: parry > guard-break > block > attack > parry). Attempts consumeParryCooldownhit or miss, so spam can’t blanket a swing. Hit frames sweep range + facing arc with a fail-openMelee.CanHithook for friendly-fire policy. Players ride auto-wired fail-closed intents (ML_Attack/ML_Block/ML_Parry— “attack while staggered” dies in validation); combatants are entity-agnostic (sessions, npc handles, models), and npc victims with no manual parry roll their own skill-scalednpc:defend()— difficulty-driven NPC parries for free. Bus:Melee.Hit/Blocked/GuardBroken/Parried/Staggered/Combo/State. See MeleeKit - MoveKit: abilities bound to movement context — “jump pressed while airborne” as a first-class trigger: abilities are a trigger (
Input = "Jump"ridesUserInputService.JumpRequest, other strings ride InputDriverInput.<Action>bus events, orkit:trigger(name)) plus a context gate (Grounded/Airborne/Rising/Falling/custom predicate over{State, AirTime, Rising, UseIndex, ...}), a charge pool that refills on landing, a cooldown, and a takeoff debounce (MinAirTime). Recipes:doubleJump({Power})(fresh scaled jump mid-air) andairStep({Steps, Boost, OnStep})(anime footholds — kills the fall, hops off the invisible platform,OnStep(position, useIndex)draws the glow pad). Ownership guarantee intact: the CLIENT executes (it owns its character’s physics),MoveKit.servermeters through the fail-closedCKMoveintent with its OWN charge/cooldown books and its OWN ground probe (a client that lies about landing gets no fresh charges);Forgive = truepardons the next anti-exploit sample for horizontal bursts. Bus:Move.Triggered(client),Move.Used/Move.Rejected(server). See MoveKit - ProjectileClient fairness rule: no invisible shots —
MaxVisualsnow caps only the FULL cosmetic visual; overflow spawns render as minimal pooled neon tracers instead of being skipped, and any shot whose flight path passes withinThreatRadius(default 15 studs) of the local character renders FULL regardless of the cap — an incoming round the target can’t see is a fairness bug, not an optimization.MaxTracers(default 300) bounds pathological spam (threatening shots bypass every cap). New pureProjectileClient.threatens(origin, velocity, position, radius)+ spec. See Projectiles - DeviceBench: granular per-axis device benchmark, computed quality budgets, and a 60fps governor — three time-boxed Luau workloads (compute, allocation churn, coroutine resumes) measure how fast the client actually runs, and each becomes a CONTINUOUS 0.25..4 axis multiplier against a mid-range reference, decimals fully allowed; the geometric mean is the overall
Quality. Budgets are FUNDED PER AXIS — the workload a device is good at pays for the systems that stress it: strong Churn buysParticleBudget/VfxDensity, strong Compute buysBoneChains/BoneDistance/ProjectileVisuals, strong Resume buysAudioChannels— so a device that benched 3.4x on churn but 1.2x on compute gets near-max particles while bone chains stay modest.profile()computes the budgets from a Result, any exact quality number (profile(1.35)), or a tier name;Low/Medium/High/Ultratiers remain coarse conveniences forpick(). Client modules consume the profile when unconfigured: BonePhysics cullMaxDistanceand NEWMaxChainschain budget (past it the chains nearest the camera win, the rest freeze at their pose), AudioKitMaxChannels, ProjectileClient NEWMaxVisuals(pooled-render cap; hits still land past it). Every default is an overridable option and every cap is purely cosmetic.scale(min, max)is log2-mapped over the band — every doubling of device quality buys the same slice of the knob. The GOVERNOR holds a stable frame target at runtime — 60fps first, beauty second: it watches p95 frame times and nudges the effective quality ×0.8 after ~3s of sustained misses, ×1.15 back after ~10s of clear headroom — granular nudges, not tier cliffs — capped at the benched quality (frame luck is not capability), with axis ratios preserved while dialing (a churn-strong device dialed down still favors its particles); changes arrive viaOnChange(quality, profile)andDevice.QualityChanged. Time-boxed means weak devices run fewer iterations in the same ~90ms instead of hitching longer; results cache, reportPlatform, optionally sample a medianFrameMs, publishDevice.Benchmarked, and auto-run on first helper use — pay the bench during loading (pairs with Preload: asset warming is network-bound, the bench uses the idle CPU). A client measurement — cosmetics only, never authority. Reference constants are provisional pending a live calibration pass. See DeviceBench - NPCKit is now HELPERS, not an AI runtime — the behavior system is gone: no priority-list
{When, Step}runner, no kit-owned tick loop, no built-in behavior factories (wander/idle/chase/investigate/hide/cover/strafe/attack/suppress/flank/flee/tree all deleted). The kernel ships syscalls, not game rules — and chase/cover/flank policy IS game rules. What remains is a thin spawn/despawn lifecycle (model clone, health, died-cleanup, zones tracking) plus per-npc state, and YOUR loop composes the helpers:npc:updateTarget(acquisition/sight memory/graded detection),npc:canSee/npc:sweep(cone-gated eyes, pie-slice fans),npc:aimAt(the full skill model),npc:act(movesets),npc:defend,npc:moveTowards/npc:faceTowards,kit:findCover/kit:findPeekPoint(cover SCORING without the phase machine — Direction-attribute nodes, BackAway ground-giving, squad spread all kept),kit:all()(the roster your loop walks), andkit:update()(squad director + due melee windups, called from your tick). Two new standalone helper modules:Senses— hearing with levels of ATTUNEMENT (Keen/Sharp/Average/Dull/Obliviousor any custom{Range, BlurStuds}), anisotropic blur (direction resolves well, distance poorly), and aDistanceoverride so route-measured occlusion plugs in; plusinCone/canSeevision gates — NPCKit’s own ears and eyes delegate here, so kit npcs and hand-rolled agents perceive identically.Pathfinding.follower— per-agent re-pathing/waypoint-following as a handle your loop steps (follower:step(position, goal) -> nextPoint, {Path?, Jump, Stuck}): goal-drift/grid-version re-paths, failed-search backoff, stuck watchdog, jump hints;npc:moveTowardsis now a thin wrapper over it and applies the point via Humanoid/injected Move.BehaviorTreestays as the pure strategic helper — build one per npc, tick it from your loop with the kit clock. See NPCKit, Senses, Pathfinding - NPCKit trained-combatant pass: graded detection, pain response, ammo discipline, honest gunfights — sight can now charge instead of snapping:
DetectionSecondsonupdateTargetturns acquisition into an accumulator (fills faster up close and against fast movers, decays unseen; half charge publishesNpc.Suspiciousand points the hearing memory at the glimpse so your search logic walks over for a look, full charge acquires), and re-sighting a target after real occlusion re-imposes HALF the reaction time — the reappearance is a fresh stimulus, not a free instant re-engage. Getting hit registers:npc:notifyDamage(attacker?)(called automatically by kit Hitscan/Melee on npc victims, wired to Humanoid damage on spawned rigs, published asNpc.Damaged) suppresses aim for 1.5s (readState.SuppressedUntilto duck an exposed peek) and drops an unseen attacker’s position into the hearing memory — a back full of bullets now turns the body around. The aim model grew up: leading uses the TRUE intercept solve (|delta+v·t| = s·t) instead of distance/speed, error is correlated drift + near-Gaussian jitter (bursts walk readably instead of statistically bracketing the target), continuous time-on-target tightens the cone, and suppression blooms it. Movesets:Magazine/ReloadSecondsforce the reload pause players push into (Npc.Reload), meleeWindupSecondstelegraphs the swing clock-driven with a real dodge window (Npc.Windup; landing re-checks the arc), and ranged actions in a squad HOLD FIRE when a squadmate blocks the lane (FriendlyFire = trueopts out).npc:faceTowardstracks targets with the body while standing still — a stopped npc’s frozen vision cone would otherwise lose a target circling behind it. The squad director fields real fireteams — nearest holds Pressure, the rest alternate Flank (sides split so the pincer closes from BOTH directions,Squad.FlankSigns) and Suppress (base of fire) — and the blackboard expires 30s-stale intel so dead men stop being suppressed and departed Players stop being retained - Pathfinding: feet-vs-photons split, one-flood hearing, off-mesh links, unstickable movers — grid line of sight split into two honest queries:
lineOfSight(walkability) now also gates CONSECUTIVE cells along the line byMaxStepHeight, so a Theta* shortcut between two in-band endpoints can no longer walk an agent off a sheer cliff a stepwise search would refuse, while newsightLine(vision — now backingNPCKit.hasLineOfSighton grids) lets eyes cross valleys and pits that block feet and only occludes on ground RISING past the band. NewPathfinding:distanceField({Grid, Origin, MaxDistance})Dijkstra-floods route distances from one origin —emitSoundPath occlusion now runs ONE search per sound instead of one full A* per listener.grid:addLink(fromWorld, toWorld, cost?)authors jump/vault edges (ledges, windows) that A*/Theta* traverse but never Theta*-shortcut across; humanoid movers jump for waypoints rising past step reach. Movement robustness: failed searches back off 1s instead of re-burning the full expansion budget every tick against an unreachable goal,Grid.Version(bumped byrefresh/refreshRegion) invalidates cached npc routes when the map moves under them, a stuck watchdog (no ground covered for a second while trying) forces a repath plus an unstick hop, andPathMethod = "Roblox"computes off-tick —ComputeAsyncyields could previously stall the entire npc population from one navmesh brain. Also: any-angle search uses the euclidean heuristic (octile overestimates euclidean segment costs — inadmissible), the ground raycast setsRespectCanCollideso foliage decor stops rasterizing as floor, and waypoint arrival radius scales with cell size. The default rasterizer stopped reading transient BODIES as geometry — a cell probed while a player or npc stood on it cached as blocked (or as walkable ground at head height) forever, including the asker’s OWN start cell — and its clearance column now starts at step height instead of the floor, so a 1-stud lip or shin-high debris no longer dead-strips the ring of cells around it (a SpawnLocation’s edge used to island the whole spawn platform). Newgrid:nearestWalkable(position, maxCells?)— the forgiving front door for user-driven queries: clamps off-grid points into bounds and snaps clicks on top of walls to the nearest standable cell, ring by ring, instead of failing onGoalOutsideGrid/GoalBlockedtechnicalities. The clearance check also grew a NARROW PHASE:GetPartBoundsInBoxmatches BOUNDING boxes, and a wedge ramp’s bounding box is a full-height slab that blocked every cell of its own slope — non-block shapes (wedges, cylinders, meshes, unions) now confirm against real geometry with footprint-sampled down-rays, so ramps rasterize as the walkable slopes they are. See Pathfinding - BehaviorTree: reactive preemption + honest clocks — new
ReactiveSelectorre-evaluates from the FIRST child every tick so a higher-priority branch coming alive preempts a Running lower one (whose resume memory resets) — the right root for combat brains, where “enemy appeared” must interrupt “investigate” mid-plan; plainSelectorkeeps the resume-directly semantics.tick()threads an optional clock intoCooldownnodes (pass the NPCKit kit clock for determinism under spec, without the global_setClockswap), andreset()is the documented move on target change — a plan mid-flight against the old target is a bug against the new one. See BehaviorTree - NPC determinism + correctness touch-ups —
defend()rolls reactions in name-sorted order (hash-order iteration made WHICH reaction answered nondeterministic under a fixed Seed), hitscan/sweep victims resolve through nested prop models to the HUMANOID-bearing ancestor, and npcs ignore their own noise —Sounds.Topicscan safely listNpc.Hitscanso NPC gunfire alerts OTHER npcs without the shooter investigating itself - NPCKit tactical layer: squads, directors, and behavior trees — archetypes take
Faction(faction-mates never target each other) andSquad(auto-join a shared blackboard): members report what their senses ACTUALLY produced (sight fixes exact, heard positions blurred by the listener’s skill) viasquad:report, readable throughsquad:lastKnown(target)/npc:lastIntel(target); the per-squad director (stepped bykit:update()) allocates roles per engaged target, publishingNpc.Tactic(npc, role)for client animation polish — your loop readssquad:role(npc)to decide who pins the last-known fix and who curves the arc. Cover is map-authorable: tag parts (SpotTag) with an optionalDirectionattribute so a node only counts when the threat is on its covered side, and squadmates already camping a node push its score down.npc:sweep(direction, arc?, rays?, range?)slices the pie — a horizontal raycast fan across the unseen angle before the body commits. The vision cone gate is a dot-vs-cos compare that runs before every sight raycast. NewKits/BehaviorTree:Selector/Sequencecomposites with realRunning-resume memory,Condition/Actionleaves,Invert/Succeed/Cooldowndecorators,reset(), injectable clock — build one per npc so plans never share memory, tick it from your loop - NPCKit combat brain: vision cones, sight memory, defense reactions, cover scoring — sight is a cone, not a sphere: new
FieldOfViewDegreesdifficulty knob (Perfect 360 → Bad 100) gatesnpc:canSee(target, maxRange?, fov?)against the rig’s actual facing before any raycast (hearing stays omnidirectional, covering the rear);RequireSighttargeting tracks briefly-occluded targets forMemorySecondsthen hands the LAST SEEN position to the hearing memory — your loop hunts the spot like a soldier instead of pathing omnisciently through walls.kit:findCover(withBackAwayfor fighters that give ground) breaks the target’s line of sight from grid cells or tagged nodes;kit:findPeekPointfinds the corner beside a spot with eyes on the threat. Defense: per-archetypeReactions(Againstattack types, per-reactionDifficulty, cooldown consumed on ATTEMPT) rollDefenseSkill— kitHitscan/Meleeactions roll the victim’s reactions automatically when it’s an npc,npc:defend(context)exposes the same gauntlet to game code, andNpc.Defense(npc, name, success, context)publishes deflects AND fumbles. Per-actionDifficultyoverrides resolve at define-time (Perfect defense on a Novice trigger finger); anisotropic hearing blur stretches error along the sound direction (direction resolves well, distance poorly) - Ragdoll v2: rig-agnostic, build-once, owner-client replication — smooth down, clean recovery — NEW-format character rigs (AnimationConstraint joints, zero Motor6Ds) ragdoll by cutting the animated joints and TUNING the rig’s own ball sockets for the ride (friction via
FrictionTorque(default 60) + limits, everything restored on release) — frictionless free-spinning joints were the first “spazzing ragdoll”; classic rigs keep the Motor6D→limited-socket swap, now also friction-damped. The residual spazz had two more causes, both fixed: the owner client now LATCHES the Physics state (auto-getup disabled + a StateChanged re-force — ground impacts knocked the state machine into Landed/GettingUp, whose standing forces wrestle the sockets) and limbs ride an auto-registered SELF-BLIND collision group (CKRagdollLimbs: jointed-pair no-collides missed hand-vs-hip and arm-vs-leg contacts, whose depenetration impulses shook the pile). No automatic force at all: a socketed stack loses its balance the moment the joints cut and crumples on its own — explicitImpulsehit flings distribute per part, since with rigid joints cut every limb is its own assembly and impulses on the massless ghost root moved nothing. DefaultFrictionTorquerelaxed 60 → 10 (the high value was masking the state-machine and self-collision spazz, both now fixed structurally), and joint cones widened to anatomical ranges — shoulders 120, hips 90, elbows/knees 80 (tight cones read as a stiff mannequin; limits only stop the impossible). NewOptions.Limitsmerges per-joint{Upper, Twist}overrides over the built-in table, so the flop is tunable per game. The ROOT joint itself is never cut: the HumanoidRootPart is the camera subject, and cutting its joint orphaned a massless, non-colliding ghost that fell through the world — the camera dove after it, and release “recovered” the character under the map; left attached, the root rides the torso and the camera lies down with the body. Recovery uprights the root +2 studs (it lies at torso height; standing needs leg room or GettingUp unfolds into the floor). Components build ONCE per model and toggle on re-ragdolls (no instance churn on the wire). Player characters: humanoid state is client-authoritative, so the server pushes theCKRagdollstate channel andChloeKernel/Net/RagdollClient.listen(netClient)answers on the owning machine; release WAITS for the server’s joint re-enables to replicate (standing up while joints were still cut was the broken recovery), kills linear AND angular momentum, stands the root upright with yaw kept, re-enables Animate fresh, and gets up — server-simulated rigs get the same momentum-kill + upright on their release. PlatformStand applies only to server-simulated rigs — stacking it on a player character made the two controllers fight. Verified live: state holds Physics the whole ride, torso angular velocity settles to zero within a second of the kick, recovery stands upright (UpVector.Y = 1.00) straight into Running. See Ragdoll - Pathfinding: partial grid refresh —
grid:refreshRegion(minWorld, maxWorld)drops only the cells a moving wall or opening gate swept; the rest of the map keeps its raster, so continuously-moving courses re-path cheaply (the fullrefresh()stays for map-wide changes) - BonePhysics: boneless accessory sway + ambient wind —
bindAccessory(run automatically bybindCharacter) makes accessories WITHOUT bones move: the accessory becomes a swing-clamped pendulum hinged at its joint, steered through whichever joint kind wears it — Weld/Motor6D handle-side offsets, RigidConstraint handle-side Attachment CFrames (the modern catalog-hair case; nothing swapped or disabled), or WeldConstraints swapped for an equivalent local Weld and restored exactly on release.MaxSwingDegreeskeeps hair drifting instead of flipping. NewAmbientWind = Vector3sim option: a procedural breeze gusted by layered sines, no Wind function needed. Verified live on a real avatar: 10/10 accessories bound, hair swaying through its untouched RigidConstraint. See BonePhysics - NPCKit: opt-in NPC-vs-NPC —
Archetype.Targetable = truejoins the default target roster so other npcs hunt that kind through ordinary acquisition (self-targeting excluded, faction-mates filtered); overrideGetTargetsfor full roster control - AudioKit: the kernel’s all-in-one audio engine — bank-registered 2D/3D playback over auto-created bus SoundGroups (Music/SFX/Ambient/Voice/UI), with every fade, crossfade, and duck driven by ONE scheduler stepper (no TweenService churn, no per-sound Heartbeat connections, deterministic under spec). Sound instances and 3D emitter parts pool; channel pressure evicts lowest-priority-first (music is never evicted); ducks stack with deepest-wins semantics. Adaptive music mixes layered stems by WEIGHT on one locked timeline (
playLayers+setWeights/resync); cue points fire loop-awareAudio.Cuebus events;speak()queues dialogue on the Voice bus, auto-ducks, and publishesAudio.Subtitle/SubtitleEndedfor the game’s UI; reverb zones apply by listener position with priority overlap; soundscapes scatter randomized chirps over an ambient bed. Kernel-only integrations:Alertrings NPCKit ears so player-audible sounds are also NPC intel,Occlusion = "Path"muffles by the pathfinding route around geometry (walls block, corridors carry),bindSettingspersists bus volumes through the validated Settings service, andAudioKit.server/audio:listenbroadcast plays over one state channel. See AudioKit - AnimKit: the animation counterpart — bank-registered clips played through per-rig controllers with track caching, crossfading exclusive
Groups (walk -> run without the T-pose blink), marker-to-bus forwarding (Anim.Marker), and an inverse-kinematics layer:rig:ik()wraps IKControl (LookAt/Reach/Transform/Rotation) with stepper-faded weights, Vector3 targets ride a movable helper,Propertiespasses raw overrides, andrelease()hands the skeleton back. Rigs die with their models;assetIds()feeds the preloader. See AnimKit - Preload: the framework’s asset pool — collects every registered asset (
AudioKit/AnimKitbanks viaassetIds(), plus extra ids and whole Instances), dedupes, and warms them in batchedPreloadAsynccalls off-thread, narratingPreload.Started/Progress/Doneon the bus for loading screens;await()blocks a boot flow until warm; failures collect and report, never abort. See Preload - BonePhysics: a lean, leak-free SmartBone replacement (
ChloeKernel/BonePhysics, client-oriented shared module) — verlet chains over Bone hierarchies with a FIXED-timestep solver (hitch-clamped substeps; no frame-rate-dependent droop), flat arrays instead of per-bone objects, camera-distance culling that snaps back to the pose on wake, and a teleport guard so respawns don’t whip chains across the map. Poses write throughBone.Transform(the animation slot) composed against the parent’s effective world — neverWorldCFrame, whose setter permanently mutates the bone’s rest CFrame (verified empirically; that mutation is exactly the “SmartBone broke my skeleton” class of bug). Lifecycle is airtight: roots auto-unbind onDestroying,handle:destroy()restores Transforms,sim:destroy()releases everything.bindCharacterscans accessories/layered clothing for boned parts;bindPartsdrives plain part chains (stud tails) with the same solver. Settings per bind:Damping/Stiffness/GravityScale/WindScale; sim options include injectableWindandShouldSimulate - Ragdoll: Motor6D-to-BallSocket ragdolls with per-joint swing/twist limits (R6 + R15 names), collision flips (limbs drape, root goes ghost + massless), optional death wiring,
Impulseflings,Durationauto-release, and aMaxActivecap that releases the oldest pile-up.release()restores EVERYTHING it touched — motors, collision flags, collision groups,AutoRotate,RequiresNeck, humanoid state. Replication is near-perfect at zero extra cost: the joint swap is server-side and the engine streams the physics; a player’s own character keeps its network ownership (per the kernel’s no-ownership guarantee), so their ragdoll simulates locally at full rate. Bus:Ragdoll.Started/Ended(model) - NPCKit combat model: hitscan, arcs, live difficulty —
Kind = "Hitscan"actions resolve through an injectable raycast with aim error only (no lead — it’s instant) and publishNpc.Hitscan(npc, origin, hitPosition, victim?)tracers;Action.Gravityruns a ballistic low-arc solve for lobbed projectiles (out-of-range lobs 45 degrees);npc:setDifficulty(nameOrTable)swaps the skill model mid-fight - NPCKit routing skill + path events — new
PathSkilldifficulty knob (Perfect 1.0 → Bad 0.2): high skill takes any-angle Theta* and re-paths eagerly as goals drift, low skill runs plain A* and clings to stale paths; explicit kitPathMethodstill overrides. Every computed route publishesNpc.Path(npc, waypoints)so visualization/debug tooling never monkeypatches the finder - NPCKit senses: hearing —
emitSound(position, {Range?, Loudness?, Source?})alerts NPCs with range AND accuracy scaled by difficulty (newHearingMultiplier/HearingBlurStudsknobs on every preset:Perfectpinpoints the exact position,Badhears a vague 30-stud blur — the ear model itself lives in the standaloneSensesmodule),Sounds.Topicsauto-emits from listed bus topics at the acting player’s position ({["Weapon.Fired"] = 60}= gunshots make noise with zero wiring), andSounds.Occlusionpicks the geometry model:"Through"(walls ignored, default),"Blocked"(walls stop sound — the “through-wall stuff off” switch), or"Path"(distance measured AROUND obstacles on the grid, maze-correct; no route = not heard). The heard fix lands inState.HeardPosition/HeardAt/HeardSourcefor your loop’s investigate logic; a fresh sound resets the search scratch fields. Bus:Npc.Heard(npc, heardPosition, source?) - Pathfinding: terrain- and height-aware grids — the default rasterizer now raycasts for ground (Terrain, parts, and model geometry all count) and requires the agent column above it clear; cells carry their ground Y, so waypoints follow elevation, adjacent rises past
MaxStepHeight(default 4) read as unclimbable cliffs in A*/Theta* expansion, and grid line of sight refuses to cross walkable high ground — a wall top still occludes even though you could stand on it. InjectedIsWalkablemay return a second ground-Y value to opt in; plain boolean rasterizers behave exactly as before - NPCKit: server-side npc handles — archetypes carry a skill model with seven difficulty presets (
Perfectaimbot throughBad, or any custom{AimErrorDegrees, ReactionSeconds, LeadSkill, CooldownMultiplier}table, overridable per spawn) driving aim spray, reaction delay, moving-target lead, and attack pace, and movesets UNIFIED with player systems:Projectileactions fire the same kernel Projectiles definitions players shoot (npc handle rides as the owner session),Customactions receive a pre-aimed direction for pointing at existing WeaponKit/SpellKit server logic,Meleegates on range for creature attacks. Your game’s loop drives the handles (kit:all(),npc:updateTarget,npc:act,npc:moveTowardsre-pathing through Pathfinding as goals move); spawned models auto-track in Zones (Zone.EntityEntered/Left). Bus:Npc.Spawned/Died/Action - Pathfinding: one handler, four methods, each lazily required on first use —
AStar(8-connected grid, octile heuristic, corner-cut safe),ThetaStar(any-angle: parents shortcut through line of sight for straight segments),Direct(single LoS test; grid raster or world raycast),Roblox(PathfindingService navmesh; yields). Grids rasterize a world rectangle lazily with caching (refresh()after map changes), take injectedIsWalkablefor RTS maps/dungeons/deterministic specs, and searches carry aMaxExpansionsbudget with reasoned failures (GoalBlocked,NoPath,BudgetExhausted) - QuestKit: quests as data — objectives count server-published bus topics (
{Topic, Count?, Match?, Filter?}), so there is no client input to validate. Default rule counts events whose args include the session/player;Matchpins an arg (zone name, weapon id);Filtertakes over for actor-less topics.AutoAssign,Repeatable(resets fresh on completion),OnCompleterewards, progress persisted via profile with transient/persisted merge on load. Bus:Quest.Progress(player, questId, index, count, required),Quest.Completed(player, questId). See QuestKit - InteractionKit: exploit-proof ProximityPrompts — prompts are created from CollectionService tags (and follow tag changes live), but a trigger is client input, so every one re-validates distance (latency slack), server-side line of sight, and per-player cooldown through the fail-closed
Intent.Interactchain (kit gate at 50; game rules prepend) beforeOnInteractruns. Bus:Interact.Triggered(session, id, instance),Interact.Rejected(player, id). See InteractionKit - Settings: persisted player preferences behind an allowlisted schema — client writes ride a rate-limited intent through the fail-closed chain where unknown keys reject, numbers clamp, strings cap, and string arrays REBUILD clean so hidden hash keys never reach storage; values persist via the DataDriver profile (player-chosen transient values win the merge),
Settings_Syncpushes authoritative values back, andSettingsClientgives the client fetch/optimistic-set/onChanged over the same channels (wire to InputDriverrebindfor persistent remaps). Bus:Settings.Changed(session, key, value). See Player settings - Analytics: batched, sampled, rate-capped event pipeline —
track()is a queue insert, a background flush hands batches toAnalyticsService:LogCustomEvent(funnels/retention with zero infrastructure) or an injected destination (webhook/warehouse); per-event sampling and a per-minute cap protect the custom-event budget with every drop counted, never silent; optionalWatchErrorsbridgesKernel.ScriptError. Crashing destinations lose only their own batch. See Analytics Kernel:securityAudit(): one pre-ship sweep over the whole security model — flags handler-bearing channels with no validator (High), gate hook points flippedFailOpenwhere a crashing validator would PASS payloads (High),Open = trueescape hatches to re-confirm against hostile input (Medium), and channels still on the default rate limit (Info); prints a severity-sorted summary and returns findings for CI-styleassert(#findings == 0)gates.HookRegistrygainedlistPoints(). See Fuzz & audit- Zone-scoped replica interest:
replica:bindZone(zones, zoneName)— subscription flips the instantZone.Entered/Leftfires (full snapshot on entry, remove on exit, quantize baselines flushed) instead of waiting for the replica’s interest scan, which stays wired underneath viaisInsideso pre-bind occupants converge. Players outside a region pay zero bytes for its replicas. See Replica - Boot readout floors at 0.01ms — a sub-hundredth-millisecond boot no longer prints a suspicious “0ms”
- Zones: entity tracking, per-zone signals, multi-part volumes, tag wiring — zones now detect NPCs/props/vehicles alongside players:
track(entity)/untrack(returns an untrack fn) andtrackTag(tag)(CollectionService, live; untag or Destroy fires the leaves) put entities in the sweep, riding newZone.EntityEntered/EntityLeftbus topics whileZone.Entered/Leftstay players-only.add()now returns a per-zone handle carryingEntered/LeftSignals plusOnEnter/OnLeavecallback options, so handlers stop name-filtering the global topics; session-end andremove()leaves flow through the same dispatch (handles fire everywhere). One zone can span several parts (add(name, { PartA, PartB }),addPart), andaddTagged(tag)builds zones straight from Studio tags — zone name from theZoneNameattribute (fallback: part Name), same-named parts unioning into one zone. New reads:entitiesIn(name),isInside(name, occupant). InjectedQuerydoubles and the default overlap-query reuse are unchanged. See Zones - Built-in hook point & bus topic catalog — a new API-reference section documenting every hook point the framework fires (context fields, fail-open vs fail-closed vs observational, when it runs) and every bus topic it publishes (args, publisher), plus the consumed
AntiExploit.Forgivepardon topic and the hooks-vs-bus mental model (hooks ask permission, topics announce facts). Zones cookbook and API rows rewritten for the new surface — on this site: Hook Points & Bus Topics - ProceduralKit: server-authoritative runtime geometry on Roblox ProceduralModels (Studio beta) — archetype registry with fail-closed parameter validation (unknown names/wrong types reject, numbers clamp to Min/Max, OneOf enforced), per-model regeneration rate limiting (writes inside the cooldown buffer and flush as ONE batched rebuild — a spammed customization intent costs one regen per window, not one per message), session-bound lifecycle (
Owneroption),waitForGenerationsurfacingGenerationError, and a publicvalidate()for wiring client intents through the kit’s rules. Generators stay server-side by design: the peer that changes a parameter generates and the results replicate, so ServerScriptService generators are never decompilable. Ships an ExampleGenerator demonstratingparams:Pause()timeout safety and Seed-derived determinism. Bus:Procedural.Spawned/Updated/Rejected/Removed. See ProceduralKit - Engine diagnostics that survive production: new
Diag{Role}ReplKbpsIn/Out,PhysKbpsIn/Out,Primitives,MovingPrimitives,Contactsattributes plus a panel Engine section (replication bandwidth, physics replication, awake primitives, contacts) — none gated by memory tracking, so live servers keep a full engine debugging surface. Investigated enabling engine memory tracking by default: impossible — Roblox turns it off in all production builds before any code runs andStats.MemoryTrackingEnabledis READ-ONLY with no opt-in (staff-confirmed); ALL memory getters (incl.GetTotalMemoryUsageMb) return 0 + warn when off. The panel now states this plainly where the memory bars would be instead of silently omitting rows - Frame-aware scheduler budgeting (server default): each step budgets the slack left under a 16.5ms frame target after everything else’s measured share of the frame —
Stats.HeartbeatTimeMs(the engine’s smoothed heartbeat-phase compute, idle excluded, verified empirically) plusStats.PhysicsStepTimeMs, with the kernel’s own smoothed share subtracted back out so its own work never shrinks its own budget. An idle server drains ~16ms of backlog per frame; an engine-heavy one backs off automatically, never below the 1.5msBudgetSecondsfloor; a frame-interval guard drops to the floor when frames run >25% late (covers unmeasured costs like replication serialization, and misreporting platforms). ExplicitBudgetSecondskeeps the old fixed-slice behavior;TargetFrameSecondsopts any scheduler in; clients default to fixed 1.5ms (the render thread is invisible to the frame stat); phase schedulers stay fixed so a second frame-aware scheduler cannot double-claim the same slack. See Scheduler - Frame composition diagnostics — the frame decomposed by WHO is spending it:
Scheduler.Statsrecords the live budget, heartbeat-phase busy time, and physics time every step, plus a windowed step avg/max/deferred drained bytakeStepWindow(); newDiag{Role}FrameBusyMs/PhysicsMs/FrameTargetMs/StepMaxMsattributes; both panel windows grew a Frame section splitting measured compute into engine + other scripts / physics / kernel share (client adds render CPU) with a frame-compute bar against the target and per-row diagnosis tooltips Overloadsimulation mode: ~20ms of scheduled burn demand per frame (8 tasks every step on a Low/Background mix) plus 500 entities — deliberately exceeds the whole frame target so the budget pegs,Deferredgoes nonzero, queues back up, and the red panel states show (verified live: 74-143 deferrals/window, frame compute ~18ms). HighIntensity reframed as the realistic load a modern server clears green in ~1.2ms/frame — that being visible is the point. See Simulation- Simulation harness (
Simulation.server.luau+ChloeKernelServer/Simulation): three load modes selected by theChloeKernelSimulationReplicatedStorage attribute —HighIntensity(shooter-scale stress: 200 entity processes, hook/bus floods, replica delta churn, CPU burn),SmallGame(light baseline),Showcase(narrated 6-act feature tour incl. real haste/slow on present players and an anti-dupe replay demo). Hot-swappable mid-game, full teardown, Studio-gated, fake packet factories so simulated traffic never touches the real wire. See Simulation - GcWatch: GC statistics from
gcinfo()sampling — allocation rate (the lever that drives Luau’s incremental GC cost; Roblox exposes no pause timings), GC cycle cadence, and reclaim sizes that tell leaks (heap climbs, reclaims ~0) apart from churn (heap stable, busy cycles). Auto-attached byenableDiagnostics(newDiag{Role}GcAllocKbS/GcCyclesMin/GcReclaimKbattributes), graded in both debug-panel windows, and reported in the simulation heartbeat. See GcWatch - SimPanel + SimLoad (
Debug/): a SIM CONTROL window that switches server simulation modes live (Studio-onlyChloeKernelSimControlremote, mode-whitelisted) and runs client-side loads on the local machine — SimLoad is the role-agnostic rig set built on shared primitives only (processes, hooks, bus, serde, pools, burn) with its own GC-reporting heartbeat. Wired through the test place’s client Bootstrap; delete that block for a real game. See Debug panels - DataDriver backups (
Config.Backup): rotated periodic copies to any second backend (DataStore{Name}_Backupsby default; Memory/Http/custom for external DBs), optional per-saveMirrorto a live slot, and load-time fallback that restores the newest readable backup when a record is corrupt or unmigratable (profile.RestoredFromBackup+Kernel.ProfileRestoredbus event). Exactly-once per key per interval game-wide with zero coordination — backups ride the session-lock owner’s save path and the schedule lives inside the record, so no server count can duplicate them and server hops don’t reset the clock. Restore never bypasses session locks (corrupt ≠ unlocked) and never runs while the backend is down. New APIs:backupNow,listBackups,peekBackup(read-only, lock-free);Config.Clockinjectable for tests. See DataDriver - SOLID untyped-value support:
"Any"is a first-class Serde schema type (adaptive best-fit per value, clear errors for unstorable kinds);Serde.schema(spec, { Extras = true })preserves fields absent from the schema adaptively instead of silently dropping them;Serde.infer(sample)builds a typed spec from plain data with safe wide widths (integers → U32/S32, floats → F64 — never narrowed from a sample’s magnitude); DataDriver acceptsCodec = "Auto"(inferred from Defaults, extras on). See Serde - Debug panel NET window + NetTap: a live wire inspector for kernel channel traffic from both machines — direction, channel, decoded args, payload bytes computed from schema widths, server processing ms / request round-trip ms, and verdicts (OK/Rejected/RateLimited/NoSession/Dropped incl. Packet flood drops and oversized unreliable sends). Capture is pausable (freeze the tail), clearable, and filterable (replica noise toggle); clicking a row pins it in an Inspector that shows the message both ways — decoded argument values AND the serialized payload as hex, generated through the same Packet type engine that writes the wire. Server entries reach the client on a debug-only mirror channel that never exists in production. SimPanel’s control path is now a real kernel intent, so mode clicks demonstrate the full pipeline in the window. ImGui gained a
selectablerow widget. See Debug panels - Panel limit bars (consumed vs permitted): both scheduler sections show a frame-budget bar (
used / budget ms (%)— 100% is where tasks start deferring; budget published via newDiag{Role}BudgetMsattribute) and the server memory row reads against Roblox’s 6.25GB process cap; ImGui bars now support hover hints - Replica
Quantizedeadbands ({[field] = threshold}): writes that moved less than the threshold from the last replicated value update server data but skip the wire entirely — drift is bounded by the threshold, so slow movement still replicates once it accumulates. Sub-visible jitter on chatty fields (positions, progress bars) now costs zero bytes. The NET window’s replica rows also show per-recipient size (21B each x30) so fan-out totals don’t read as one large buffer - Packet ported into the kernel (
ChloeKernel/Net/Packet): no more external package — the wire transport is now kernel-maintained, formatted to kernel standards, and bug-fixable in place. Attribution to Suphi Kaner (5uphi) retained in CREDITS.md; thePackages/folder is gone and all requires point inside the kernel. See Packet & wire types - Serde fast engine (techniques from light/holy by @hardlyardi, MIT): schema and struct codecs now compile per-type writers that resolve byte widths at compile time, allocate once at the exact size, and run with zero capacity checks and no global-cursor swap — fixed-schema encode 463ns to 285ns (38% faster), decode 514ns to 367ns, dynamic schemas 22-27% faster, replication deltas at 270/293ns. Output is byte-identical to the wire-engine path (spec-asserted with cross-decoding); schemas using uncovered types (Any, CFrames, sequences, Extras) fall back automatically, so no stored record or wire format changes
NumberVlqwire type: variable-length unsigned integer (7-bit continuation, up to 2^53) — 1 byte under 128, 2 under 16384; usable in schemas, structs, and channels; clamps like the other integer types- selene std extension (
buffer_bitops.yml) teaching selene 0.28 aboutbuffer.readbits/writebitsinstead of file-level allowances - BusBridge fulfills the Bus’s advertised NetBridge:
ServerTopics(wildcards) auto-forward to every client as ordinary bus publishes,Bus:publishRemoteforwards any topic explicitly, and client publishes ride a rate-limited intent through exact-name whitelisting (fail-closed, no wildcards upward) plus theIntent.BusPublishhook chain, arriving as(topic, session, ...). See BusBridge - MessageDriver: cross-server events over MessagingService — Serde-packed + base64 payloads, a clear error at the ~1KB ceiling, publish retries with backoff, per-message crash isolation for subscribers. Completes the trio: MemoryDriver = state, DataDriver = storage, MessageDriver = events. See MessageDriver
- Net Registry: single-source channel definitions consumed by both sides (
Registry.define/server/client) — schemas can never drift; declarativeValidaterules (Range/OneOf/MaxLength) compile into the fail-closed hook chains at priority 5;Net:onRequestadded for late handler attachment. See Registry - Server log tail in the panel: server Logger lines mirror to the SERVER window on a debug-only channel (same gate/pattern as the NET mirror), giving both windows a live tail
- SimLoad real-wire flood: client load modes fire the SimNoise kernel intent at rate (HighIntensity deliberately exceeds its 50/s limit so RateLimited verdicts demonstrate live in the NET window); NET byte estimates now cover
NumberVlq Kernel:shutdown(): full teardown — diagnostics, GcWatch, services, phase schedulers, the main scheduler, and the bus — and clears the boot singleton, so a freshboot()on the same modules works (tests, in-place re-runs)ChloeKernelDebugUserIdsallowlist: comma-separated UserIds restricting the live-server debug surface per user — panel attach, the NET/log mirrors (now per-recipient sends, neverFireAllClients), and the SimControl intent- Teardown across the board: ProjectileClient
destroy()(+ client-side safety TTL and serial-wrap recycling), ReplicaClientunlisten()/destroy(), ImGuiWindow:destroy()(drag connection severed), Panel/NetPanel detach handles, Effectsdetach(), Zonesdestroy(), ErrorWatchdestroy(), Matchmaking destroy now unsubscribes everything - LiveConfig override deletion:
set(key, nil)removes the key from the shared document and every server reverts to its default on the next poll (previously a removed override stayed stale until restart). See LiveConfig - Player records persist
Meta.UserIds, forwarded by DataStoreBackend toUpdateAsyncas the metadata Roblox uses for GDPR erasure tracking - CI: rojo build smoke test and luau-lsp strict type analysis (non-blocking until the
(self :: any)escape hatches are typed away); lint paths widened to all ofsrc; leftoverHello.luauscaffolding removed - InputDriver multi-key bindings:
Keyboard/Gamepadaccept oneEnum.KeyCodeor an ARRAY of them, so one action can ride several physical inputs;rebindandgetBindingssnapshots mirror the shape. See InputDriver - Typed public surface for Studio autocomplete:
ServerKernelfields carry concrete types (Scheduler/Hooks/Bus/Services/NetDriver instead ofany),kernel:net()returns a typedNetDriver(the module loads at boot purely for its type — driver CONSTRUCTION stays lazy),session.Profileis typed asData.Profile, andregisterService/spawnProcessparameters are annotated - Intents/requests are now fail-closed on a MISSING validator, not just on a failing one. A channel with a
Handlerbut zero validators rejects every payload unless explicitly marked{ Open = true }— closing the footgun where an unguardedGrantCoins/SellItemintent let a client mint currency or set a stat at will (an absent validator used to mean “accept anything”). DeclaringValidaterules counts as a validator;Open = trueis the audited escape hatch for genuinely no-auth channels (payload-less toggles, idempotent ready-ups). Surfaced three ways: a one-time[NetDriver] ... fail-closedwarn naming the channel, anUnguardedverdict in the NET panel, andNet:auditValidators()for a proactive post-boot sweep.Registrychannel defs gainedOpentoo. Existing kit/Registry channels are unaffected (they all register validators); only handler-bearing channels you never guarded change behavior — in the safe direction, visibly, in dev - The whole tree type-checks clean (1116 → 0 luau-lsp diagnostics) and the CI analysis step is now BLOCKING: Studio’s script analysis shows zero squiggles across the framework. Fixes were annotation-only — named option-type locals for the
options or {}union trap,ImGui.Ui-typed render callbacks,:: { any }on the deliberately heterogeneous Static/Enums templates, expression-level casts where the old solver loses refinements. Spec files moved to--!nonstrict(their fakes are deliberately partial doubles; the framework itself stays strict). luau-lsp pin corrected to a real release (1.68.0) - NET inspector shows per-argument wire types: pinning a row now lists each argument’s wire type, and
"Any"arguments expand to the adaptive engine’s actual best-fit pick (Any:U8,Any:F32,Any:Table{Wave:U8}, …) — verify at a glance that values ride the optimal width. Mirrored server entries carry it too (NetTap.describeWireis public) - Rewind’s
MaxFutureSecondsdefault raised 50ms → 200ms: realGetServerTimeNowskew plus frame timing on rough connections could push honest stamps past 50ms and reject asFutureTimestamp; a future stamp only clamps to the newest sample, so the looser default costs nothing. See Rewind - Live servers no longer flood the console with “Memory tracking is currently disabled” warnings: the panel’s Total/Script-memory rows and the diagnostics MemoryMb attribute now check
Stats.MemoryTrackingEnabledfirst (live servers run with tracking off; Studio always tracks) — the panel shows a dim explanatory row instead and the SERVER window simply omits the bar - Hitbox visualization (
ShowHitboxesattach option /lag:showHitboxes(enabled)): everycastRaydraws the rewound capsules it tested — green for the hit, red for tested-and-missed — as short-lived server parts every client sees. Visual only, zero cost when off.Rewind.drawHitboxis public for game tooling - Projectiles capsule mode: setting
Hitbox = {Radius?, HalfHeight?}on a projectile definition switches PLAYER hit resolution from raw character-geometry raycasts to the same capsule hitboxes the hitscan path uses (consistent per-weapon forgiveness knobs), and the landing step draws the tested capsules through the attached Rewind’sShowHitboxesdisplay. Definitions withoutHitboxkeep the legacy behavior exactly; in capsule modeOnHitreceives{ Position, Instance }instead of an engine RaycastResult - Lag-compensated hit DETECTION with per-weapon hitboxes:
Rewind:castRay(shooter, origin, direction, range, timestamp, hitbox?)casts a shot against every tracked player’s REWOUND capsule (pureRewind.rayCapsuleunderneath, spec-verified) — previously only the CLAIM was lag-compensated while the actual raycast ran at live positions, so high-ping shots missed strafing targets the shooter had dead-on. WeaponKit uses it wheneverconfig.Rewindis attached, with per-weaponHitbox = { Radius?, HalfHeight? }sizes (default 1.6/2.4); world geometry still blocks at its current state — walls don’t rewind, and live character bodies never block shots aimed at where targets USED to be
Security
Section titled “Security”- Decode paths hardened against hostile bytes (wire and storage): length prefixes are bounds-checked against the remaining payload BEFORE any allocation — a 5-byte payload with a crafted U32 prefix could previously force a multi-GB
buffer.createon the server. Adaptive table decode is depth-capped (48), unknown packet/adaptive-type/enum/static ids error with clear messages instead of nil-indexing, VLQ reads cap at 8 continuation bytes, and the client receive loop is pcall-isolated like the server’s - BusBridge reflection closed: a topic whitelisted in
ClientTopicsthat also matched aServerTopicswildcard reflected the serverSessionobject (plus attacker-chosen args) to every client; client-originated publishes never auto-forward down anymore
Fixed (data safety)
Section titled “Fixed (data safety)”- HttpBackend treated 404 as success on every method — a misconfigured BaseUrl or changed route made every save report OK while writing nothing; 404 now only means “new key” on GET, and PUTs carry
If-Matchwhen the API returns an ETag so a concurrent writer loses the CAS instead of last-write-wins - A player leaving during a slow profile load orphaned the profile forever (the session teardown had already run,
bind()threw, autosave kept the lock fresh, and the player was kicked on every rejoin until the server died); the load now releases immediately when the session ended mid-flight - Autosave ran inline in the scheduler step, stalling every other kernel task (projectiles, sweeps, zones) for the duration of DataStore round-trips including retry ladders; it now runs off-thread, paces saves across a quarter of the interval, and never overlaps itself.
DataDriver.newassertsLockTtlSeconds >= 2x AutosaveSeconds— the autosave is the lock heartbeat - One in-flight save per profile: autosave,
save(), andrelease()could interleave retry ladders, landing an older snapshot after a newer one or re-locking a key the release just unlocked; release deactivation now happens inside the mutex - A failed final save was dropped silently; it now retries in the background (5/15/30s) before giving up loudly, and
releaseAllbails before BindToClose’s 30s hard kill with a warn naming every unsaved key - Schema downgrades refuse to load: a rolled-back deploy no longer runs old code over newer-schema records — and the refusal is non-recoverable, so it can never trigger a backup restore over newer data
- Receipts could acknowledge an unsaved grant: a re-invocation during the save window hit the ledger check and returned
PurchaseGrantedwith zero persistence (a crash then lost a paid grant); acknowledgement now requires a landed save on every path, grant handlers roll back partial mutations on error, and the dedupe ledger default grew 50 → 200 - Transactions: replay ids persist in
Data— written atomically with the trade itself — so DuplicateTransaction protection survives rejoin/server hop; a throwingsave()can no longer leak both InFlight locks findUnserializablerejects sparse arrays and non-integer numeric keys (they do not survive a JSON round-trip); MemoryDriver’s buffer probe no longer condemns the driver to base64 on a transient error, andgetRangereports a corrupt entry instead of throwing
Fixed (kernel + networking)
Section titled “Fixed (kernel + networking)”- Process suspend/resume race double-stepped forever: resuming before the queued step drained scheduled a second copy and both chains re-enqueued themselves; a
StepQueuedflag keeps exactly one chain. Self-kill no longer abandons the suspended thread unclosed or double-finishes as Completed; the PID registry is weak-valued so dropped processes collect - Scheduler recurring tasks no longer backlog under budget starvation — a starved 0.1s task could queue ~10 copies that ran back-to-back the moment budget freed, exactly the burst the snap-forward comment claimed to prevent
- Predicted-intent acks ride AFTER the handler with error isolation: “accepted” now means “applied” — a throwing handler acks a rollback instead of leaving the client’s optimistic effect diverged from a server that changed nothing. Request handlers pcall to
RejectValue, and a timed-outinvokeresumes withRejectValueinstead of nil (one failure encoding) - Rate-limit buckets keyed to departed Players could be recreated after session cleanup (Packet dispatches deferred); the session check now precedes bucket creation
Registry.predict()builds one handle per channel per client — repeated calls previously shared the ack channel while running separate sequence spaces, so an ack for one handle resolved (or rolled back) the wrong handle’s entry- MessageDriver’s 1KB guard measures the base64 payload — capping raw bytes passed 769-950-byte messages the service then deterministically rejected after burning the whole retry ladder
- Replica interest joins converge every view: pending sub-threshold quantize drift flushes to existing subscribers when a snapshot is taken (the documented “threshold is the maximum drift” bound previously degraded to ~2x for joiners), same-tick joiners skip the duplicate delta, and no-op scalar
set()s stay off the wire entirely - HookRegistry fires over a snapshot — a handler unregistering itself mid-fire shifted the live array and silently SKIPPED the next handler; in a fail-closed validation chain that is the wrong failure mode
- ErrorWatch requires Logger relatively (a cloned kernel silently shared the original’s Logger), prunes its dedup table (interpolated error messages each minted a permanent key), and stores its connections
Signal:destroy()resumeswait()-parked threads instead of leaking them suspended forever;Pool:release()errors on a double release (two acquires could share one Instance);expect().toEqualsurvives cyclic tables; a throwing InputDriver handler no longer suppresses the action’s Bus publish; Bus stops caching matchless dynamic topics and clears its wildcard cache on destroy; Logger sink dispatch reuses one thread instead of allocating a closure + coroutine per log line per sink
Fixed (serialization)
Section titled “Fixed (serialization)”String/Bufferlength prefixes are enforced in both engines — a 300-byte value previously wrapped its U8 prefix to 44 and silently desynced the whole stream (documented as “max 255”, never checked)- Adaptive fractional numbers take the F32 form only when it round-trips exactly —
decode(encode(0.1))now returns 0.1 (F64), keeping the “smallest width that holds the value” promise honest Static1/2/3error on unregistered values both ways instead of writing index 0 and decoding to a silent nil;EnumItemerrors clearly on unknown types/values;UDim/UDim2Scale clamps at the S16 field’s range instead of overflowing; the Packet factory errors past 255 channels (U8 wire ids would silently collide); the shared-cursor no-yield invariant is documented at both engines
Fixed (gameplay)
Section titled “Fixed (gameplay)”- Matchmaking dequeues tombstone their queue entries (MemoryStore queues can’t remove by value): coordinators on every server filter reads against the tombstone map, consume poisoned reads, and re-queue live members — no more undersized matches built around ghosts. Match teleports retry with backoff and re-queue stranded members, publishing
Matchmaking.TeleportFailed - SoftShutdown covered no reserved servers: Matchmaking match servers matched the transit-server check (reserved, owner 0) and got NO BindToClose protection; the bounce decision is per-player join data now, every reserved server gets shutdown protection, and bounce/transit teleports retry
- Movement’s teleport threshold prorates with the sample gap — a server hitch no longer reads a fast-but-legit runner crossing 80 studs over several missed sweeps as a teleport; rubberbands preserve the character’s facing
- Rewind buffers reset on respawn and on
AntiExploit.Forgive— a buffer spanning death → spawn interpolated between the two and rejected honest post-respawn shots as OriginMismatch. WeaponKit anchors the muzzle to the shooter’s REWOUND position (ping x move speed no longer eats the 8-stud origin tolerance) and consumes the fire-rate window only when the whole validator chain passes - CheckpointKit no longer races the async profile load: the stage field initializes lazily per touch instead of eagerly at join (where it pinned session data and crashed the validator once the profile attached without the field); progress earned during the load window merges into the profile on
Kernel.ProfileLoaded - Effects state is per-instance — the Simulation showcase attached a second Effects to the real kernel and its sweep removed (and its recompute clobbered) the game’s own effects; ActorPool surfaces worker require failures (
CKDeadattribute + clear dispatch error) instead of burning a full timeout per dispatch forever, and guards dispatch-after-destroy
Fixed (ImGui)
Section titled “Fixed (ImGui)”- Stale-click delivery across relabeled buttons: widget slots are reused by call order, so a dynamic button list could hand a pending click to a DIFFERENT logical button on the next render (mode buttons relabel exactly like this). Buttons now drop pending clicks when their label changes
Performance
Section titled “Performance”- Projectiles capsule mode hoists per-tick work out of the projectile loop: one world-pass exclude list and one alive-target snapshot per step, shared by every projectile (was a rebuild plus a
FindFirstChildsweep per player per projectile per tick), and the default raycast reuses a singleRaycastParams, reassigningFilterDescendantsInstancesonly when the list identity changes - Process steps stopped allocating a queue entry + handle per resume: new
Scheduler:scheduleEntryre-enqueues one caller-owned entry per process for its whole life (the handle was never used). A/B under HighIntensity: GC alloc 4.4MB/s -> 0.6MB/s (7x) with step times statistically identical Bus:publishno longer allocates on unmatched topics (shared frozen empty match) and the wildcard match cache is bounded at 512 entries (reset-and-repopulate past the cap), so dynamic per-entity topics that match a broad wildcard cannot pin one cache entry each forever- Replicas of the same tick rate share ONE recurring scheduler entry — the scheduler walks every recurring entry every frame, so a hundred 10Hz replicas now cost one slot in that scan, not a hundred
- Rewind samples cache the HumanoidRootPart per character (
FindFirstChildon spawn/respawn, not SampleHz times a second per player); Zones sweeps build one character include list per sweep with one reusedOverlapParams;Logger.emitearly-outs before building the entry when the console is off and no sinks exist - Process stepping no longer allocates a closure per resume. Every yield re-scheduled via a fresh anonymous closure, which missed the profiler’s function-identity cache on every step (full
debug.info+ string cost, the same class the 0.2.0 profiler fix addressed) and fed the GC a closure per process per frame. One stable step closure per process now: under the HighIntensity simulation (200 entities + bolts) server step time dropped 1.33ms → 0.50ms and GC alloc fell ~20% (4.9 → 3.9MB/s) - Hot tasks attribute process time to the process, not the kernel. New
Scheduler.setTaskOrigin(fn, label); Process registers each step closure asProcess <name>, so the panel now readsProcess SimBolt 19.8ms/sinstead of lumping everything intoProcess:89— which also revealed the real cost driver in the sim (boltPart.CFramewrites, 3× the cost of 200 pure-math entities)
Changed
Section titled “Changed”- License: private access. ChloeKernel access is granted individually by Chloe and is revocable; granted users may not share the source or grant access on her behalf. Game-use grants, required CryptedChloes attribution, the no-redistribution rule, and the vendored Packet exclusion are unchanged. Public docs live in ChloeKernel-Docs; the docs being public grants no framework access
- README rewritten in reference-documentation tone: introduction/pitch phrasing removed, stale spec counts fixed, and examples reworked to demonstrate features per config field (neutral names, annotated gates) instead of telling game stories — the intent example now shows schema/rate-limit/validator-chain/handler explicitly, and the kit examples document what each field enforces
- Deferred was invisible during overload:
TasksDeferredwas reset at step start and published by a task running INSIDE a step, so it structurally read zero exactly when overload happened (and under hard saturation the Background-priority diagnostics task stopped running entirely). Counts now finalize at step end (readers always see the last completed step) and SUM into the diagnostics window; the diagnostics publisher andKernel.Overloadstreak moved to Kernel priority — health reporting must survive the very overload that starves Background work - Server step diagnostics no longer phase-lock:
Diag{Role}StepMspublished a single last-step point sample; at a 1s diagnostics cadence against 0.1s recurring work the sample aligned with the same frame type for a whole session (reading ~35% of budget one boot and ~180% the next with nothing changed). Now a windowed average plus the newStepMaxMs - Vendored Packet (marked mod #5): the server flush loop is pcall-isolated — a
FireClientthat throws (a player disconnecting inside the PlayerRemoving cleanup window) killed the while-true flush thread and silently stopped EVERY outgoing packet for the rest of the server’s life - Effects/Zones sweeps collect first, fire after: expiry removes and enter/leave publishes ran synchronously mid-iteration, so a subscriber that re-applied an effect (or added/removed zones) inserted keys into the very tables being iterated — undefined behavior in Luau iteration
- Transactions commit in place, preserving table identity:
profile.Datawas swapped to a new table on commit, silently orphaning any reference game code held to it (or any cached subtable); commits are now deep in-place merges, so every identity survives the trade, and rollback restores from a pre-commit deep backup - Prediction acks
task.canceltheir timeout thread instead of letting it idle out the window; ReplicaClient pending never-listened replicas are capped at 64 with oldest-first eviction and a warn - Logger/ErrorWatch specs disabled the Logger console sink and never restored it, silencing ALL console logging for the rest of any Studio session that ran the boot suite (found by the simulation harness’s missing heartbeat)
- Vendored Packet (marked mod #3): the
Anyengine’s string/buffer writes used a U8 length prefix, so values over 255 bytes wrapped the length byte and silently corrupted the stream. Long values now use new type ids 29/30 with a U32 length; short values keep the original framing (stock payloads decode unchanged) - Packet F16/F24 halved values at power-of-two boundaries (mod #4): the writers put the rounded mantissa straight into the bit field, so any value whose mantissa rounded up to a power of two (63.99, 127.99, 255.99 — observed as health values collapsing:
63.99 -> 32) overflowed the field to 0 with an unbumped exponent and decoded at HALF its value;Vector3F24components were equally affected. The carry now bumps the exponent (63.99 encodes as 64), clamping at each format’s max. Sub-normal magnitudes (below 2^-14 F16 / 2^-30 F24), which previously wrote a negative exponent field, flush to zero
0.2.0 — 2026-06-09
Section titled “0.2.0 — 2026-06-09”The gameplay-and-tooling release. Everything verified live in Studio play sessions; 196 specs at every boot.
- Replication: ReplicaService/ReplicaClient — interest management, per-replica tick rates, field-indexed deltas (
Serde.struct). See Replica - Gameplay: RoundKit (phase state machine), Projectiles (server-simulated, lag-comp validated, pooled client visuals), Effects (stacking buffs/debuffs with a generic stat pipeline), Prediction (instant client actions with server ack/rollback)
- Networking: unreliable state channels, predicted intents, configurable Packet rate limit with flood reporting
- Utilities: Pool, Zones (leave-before-enter region events), Leaderstats, ErrorWatch (deduplicated error telemetry, now covering kernel task crashes), LiveConfig (MemoryStore-backed feature flags)
- Scheduler: engine-phase schedulers (PreSimulation/PostSimulation/PreRender), drift-free recurring tasks, per-task hot-origin profiler (~33ns/task) with MicroProfiler zones (Studio default),
queueDepths()/topTasks()introspection - Serde: auto-coercion (round/clamp/parse what’s castable, fail fast on garbage), struct delta codecs
- Debug panel (F8, Studio-only): separate CLIENT/SERVER ImGui windows, health-graded stats, diagnostic hover tooltips, hot-task pinpointing by script:line, copyable stat dumps. See Debug panels
- PerfGuard spec: order-of-magnitude perf regressions now fail the boot suite
Changed
Section titled “Changed”- Framework renamed ChloKernel → ChloeKernel
- Demo removed from the repo; Bootstraps are clean templates
- Network-ownership guarantee documented: the kernel never reassigns character ownership
- Profiler overhead regression (2848ns → 354ns/task)
- ReplicaClient unbounded pending-delta buffering
- Rewind clock incompatibility with client timestamps (
GetServerTimeNow) - Projectile time dilation under deferred sim ticks
task.wait(0)frame-cost in retry backoffs
0.1.0 — 2026-06-09
Section titled “0.1.0 — 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).