Skip to content

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.

  • SpatialKit: spatial queries as sentencesKit:sphere(center, 15):players():alive():collect() replaces the per-call-site GetPartBoundsInRadius + character walk + team check: shapes (sphere, oriented box(cframe, size), true-3D cone(origin, direction, range, fovDegrees), capsule(from, to, radius)) chain population selectors (players(), npcs() = the NPCKit roster, tagged(tag); none = players plus npcs) and filters (alive(), team(name) via TeamKit so npc Factions count, hasTag, where(fn), exclude(caster)) into terminals (collect/first/count/nearest(n) sorted by distance). Positions read live at query time; a Spatial character index broad-phases the players population when given; every shape test is pure and exported. CombatKit-style abilities, AI scans, loot radii, and objectives all build on one query. See SpatialKit
  • Curve: piecewise scaling curvesCurve.new():add(1, 100):add(5, 350):add(20, 1200) holds sorted keys and answers at(x) (Linear/Smooth/Step ease per segment; Clamp or Linear extrapolation outside the keys) and solve(y) (first-crossing linear inversion — “what level reaches 800 xp”); re-adding an x replaces its y, Curve.from(pairs) bulk-builds, keys binary-search. XP tables, damage falloff, prices, loot weights — one primitive instead of if-ladders. See Curve
  • Stages: gameplay resource streaming — zone-occupancy lifecycle (nothing to do with Roblox instance streaming): a stage binds resources to a Zones name, the FIRST player in runs Load (returns the unload closure), the LAST player out unloads after Linger (default 10s, so stepping out and back never thrashes spawns), OnEnter/OnLeave fire per occupant, and Replicas handles auto bindZone while loaded. Occupancy rides bus Zone.Entered/Left (leavers count down automatically), a Zones instance at attach seeds players already standing inside at define time, and every closure failure warns without breaking the sweep. Bus Stage.Loaded/Unloaded/Entered/Left — bridge them for client cosmetics. See Stages
  • Rng: float(max) one-arg formfloat(5) now means [0, 5); previously a lone argument defaulted max to 1, so float(5) degenerated to NextNumber(5, 1). The fork() seed derivation comment no longer claims textbook FNV-1a (it is an FNV-flavored fold that truncates to stay inside Luau’s exact-integer double range; the contract is determinism per (seed, name), unchanged). See Rng
  • AchievementKit: pre-profile progress merges on Kernel.ProfileLoaded — events landing before the profile attaches counted into session data and were then silently dropped; they now merge with QuestKit’s rule (unlocks carry over, the higher count per id wins, counts for already-unlocked achievements drop). See AchievementKit
  • Hazards: destroyed anchors actually retire — a Destroy()’d BasePart/Model keeps answering Position with its last value, so the liveness check never fired; anchor resolution now treats a nil Parent as dead and the next sweep retires the hazard (spec now exercises a real Instance, 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 poserelease() with zero/nil seconds restored CameraType and FOV but left the camera CFrame wherever the last shot pointed; it now lands back at the captured pose (Custom cameras re-solve from the character next frame regardless). See CameraKit
  • Chronicle: double attach fails loud — attaching a second recorder to the same kernel stacked wrappers (double-recording, with detach unwinding only the outer one); attach now errors until the existing recorder detaches. See Chronicle
  • Impact: DuckBus defaults to a real bus — the default was "World", which is not one of AudioKit’s default buses (Music/SFX/Ambient/Voice/UI), so pulse() with defaults errored on the first duck against a default-configured AudioKit; the default is now "SFX". The spring comment no longer claims “critically-damped-ish” — the live constants are genuinely underdamped (damping ratio near 0.5), matching the ringing the spec asserts. See Impact
  • VfxSuite + CutsceneKit: registration caps at 255, not 257 — the cap checked before incrementing, so a 256th registration succeeded with wire id 256 and only errored at play time (the id rides NumberU8); both registries now fail at register with the corrected “at most 255” contract. See VfxSuite, CutsceneKit
  • Gait: the root offset no longer compounds — the height/tilt offset composed onto RootJoint.Transform’s live value, which is the kit’s own previous write whenever no animation drives the joint, accumulating drift over a session; the stepper now tracks its last written value (post-write readback) and strips it before composing, so the offset applies exactly once whether or not an Animator rewrites the slot. See Gait
  • Gait: PlantSmoothing actually smooths — the plant/unplant setWeight calls omitted the fade argument, snapping IK weights instantly; they now pass PlantSmoothing, and the stepping path (plants, fades, root offset) gained spec coverage. See Gait
  • Rules: the gate order is documentedonce -> cooldown -> count regardless of chain order; once() means a single fire per key ever, so combining it with count/cooldown still fires at most once (for repeated-but-throttled awards use cooldown alone). Behavior unchanged; the module header and cookbook now say so. See Rules
  • CutsceneKit: Line publishes the raw string — the header no longer implies the kit calls Text; localization happens in your Cutscene.Line subscriber (send keys, localize at render). See CutsceneKit
  • HEAT debug window — the scheduler heatmap panel is gone; the CLIENT window’s hot-task list and budget bars remain the scheduler view, and the Panel’s profiler sampling reverts to its pre-HEAT form. The HOOKS window is unchanged. See Debug Panels
  • MaterialKit: real-geometry surface dissolution — where Dissolve flies voxel dots, MaterialKit erodes the actual rendered mesh: faces vanish and return in Perlin-noise rank order (optional directional Bias), verified live — the baked MeshPart renders from the live EditableMesh object, so every RemoveFace/AddTriangle shows immediately with no re-bake. bind(meshPart) opens the part’s own mesh asset (same two permission gates as HairKit, denials return (nil, reason)); bindBox(part, {Subdivisions}) needs NO asset permission — a generated subdivided box overlay hides the original (transparency restored on destroy) while collision stays on the game’s part. setProgress(alpha) erodes AND knits (a receding driver restructures the surface — removed triangles re-add against their recorded vertex ids), play({Duration, Reverse?}) sweeps on one Heartbeat stepper, OnFaceGone(centroid, normal) per removal for dust hooks, pure MaterialKit.order ranks. Local-only VFX, same broadcast stance as Dissolve. See MaterialKit
  • CameraKit: cinematic camera control as data — shots declare what the camera does (Static, Follow with exp-smoothed offset, Orbit, Rail = Catmull-Rom through CFrame points with eased progress and optional LookAt), one PreRender stepper evaluates the active shot against LIVE targets each frame, blend(shot, seconds, ease?) eases from the real current pose so moving targets stay tracked through transitions, cut snaps, release(seconds?) blends home and restores the CameraType; a dead target hands the camera back instead of freezing. Targets: Vector3, BasePart, Model, or function; per-shot FOV; pure CameraKit.ease/rail. See CameraKit
  • CutsceneKit: scenes as timelines — flat {Time, Action, ...} event arrays shape-checked at register (Shot/Blend = CameraKit, Anim = AnimKit rigs resolved from a named Rigs roster so shared definitions never hold Instances, Sound = AudioKit at the scene origin, Vfx = VfxSuite, Line = bus Cutscene.Line(text, seconds) for Text-key subtitles, custom Handlers); the server broadcasts ONE CKCUT_Play packet (U8 id + origin CFrame) and every client plays the whole scene locally; skip() jumps to the end — pending events drop, scene-started anims stop, the camera releases with a blend. Bus Cutscene.Started/Ended/Skipped/Line. See CutsceneKit
  • DialogueKit: server-authoritative conversation trees — nodes with Text-key lines, Choices with per-player Where gates (hidden choices cannot be picked — indices mean the VISIBLE list, and gates re-check at pick) and server-side Run consequences, Next chains, OnEnter node actions; every reference validates at attach. Wire: DLG_Sync state push per node ({} on close), fail-closed DLG_Choose/DLG_Advance intents gated against the session’s live node; begin() stays server-side (wire it to an InteractionKit prompt), leavers clean up without intents. Hook Dialogue.CanBegin (fail-open); bus Dialogue.Started/Node/Choice/Ended(reason). See DialogueKit
  • HOOKS debug window — every hook point live in the F8 panel: fire and rejection counts, per-point chain cost (avg/max ms measured around the live call), fires/s sparkline, fail stance and handler count from listPoints() (defined-but-never-fired gates show dimmed), pause/reset; decorates Hooks.fire and restores on detach. See Debug Panels
  • HEAT debug window — scheduler heatmap in the F8 panel: whole-scheduler step ms/s strip, color-graded per-task bars (green under 1 ms/s, amber under 5, red past it) over the hot-task profiler, per-task rolling history sparkline on click, deferred-task counter; the Panel feeds it samples so the two never fight over the shared profiler window. See Debug Panels
  • Rng: deterministic seeded randomnessRng.new(seed) wraps Roblox’s Random as a first-class object: float(min?, max?), int(min, max) (inclusive), choice(list), shuffle(list) (Fisher-Yates over a copy, input untouched), and fork(name) deriving an independent child stream from (seed, name) via FNV-1a — consuming an extra chest roll never shifts the NPC decisions; same seed, same sequence, every machine. See Rng
  • Time: one clock surfaceTime.attach(kernel) sets kernel.Time: now() (os.clock), server() (GetServerTimeNow, the claim-stamp clock), frame() (accumulated frame seconds, never pauses), and scaled() (game time honoring setScale and pause/resume — slow motion and pause become one switch instead of a hunt through every timer; the accumulator integrates scale×dt, never rewinds). Injectable step(dt) spec seam; bus Time.ScaleChanged/Paused/Resumed. See Time
  • Profile snapshotsprofile:snapshot(label?) → id, :snapshots(), :rollback(id): in-memory point-in-time deep copies of Data for admin tooling and risky-mutation guards (take one before a trade, roll back on abort); the ring keeps the newest 8, snapshots survive repeated rollbacks, session-only and never persisted — persisting a rollback is the caller’s save(). See 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 through task.spawn so a throwing rule never breaks the bus. See Rules
  • AchievementKit: persistent conditions over the Bus — an achievement is a topic, an optional Where filter, and a target Count; progress lives in profile.Data (Field default "Achievements") so it survives rejoins, unlocks fire once per player ever, Reward runs at unlock, and progress(session, id)/unlocked(session, id) read out for UI; bus Achievement.Progress(session, id, count, target) and Achievement.Unlocked(session, id). See AchievementKit
  • Chronicle: kernel flight recorderChronicle.attach(kernel) decorates the live Bus.publish and Hooks.fire (restored on detach()) and records every event and hook verdict into a ring: trace("Inventory") returns the last 30 seconds of entries whose topic or args mention it, trace(fn) filters by predicate, format() prints the timeline — “why did this player lose their sword?” becomes a lookup instead of a guess; summarization keeps names and numbers, never object references, so the recorder cannot pin sessions. See Chronicle
  • Text: localization over locale tablesText.attach(kernel, {Locales, Default?}) resolves get(player, key) through the player’s LocaleId, its language half (fr-frfr), then the default; a key missing everywhere warns once and returns the key so untranslated UI reads as its key instead of erroring; format(player, key, vars) interpolates {Name} placeholders (unmatched stay literal), pure Text.interpolate for tests; send KEYS over the wire and localize at render. See Text
  • Sweeps: phantom hitboxes for big rigs — server-authoritative attack volumes authored as root-local capsule tracks (pure functions of swing phase), reconstructed in world from the live root CFrame each tick and tested mathematically against player capsules with sub-stepping between phases, so a one-tick tail snap cannot skip a target; per-swing victim dedupe, Spatial broad phase, zero dependence on physics parts or replication lag. Bus Sweep.Hit(swing, victim, position); damage stays game-side. See Sweeps
  • VfxSuite: declarative timed VFX pipeline — multi-stage effects author as flat {Time, Action, ...} event arrays shape-checked at register (Spawn = pooled template with Lifetime auto-release, Emit = particle bursts on named emitters, Sound = AudioKit playAt, Dissolve = a Dissolve play on the spawn, anything else = custom Handlers); the server sends ONE CKVFX_Play packet (U8 id + CFrame) and every client drives the entire lifecycle locally in one Heartbeat stepper; sequences register on both machines in one shared module. See VfxSuite
  • Impact: screen-space feedback coordinatorpulse(origin, options) localizes intensity by camera distance (quadratic falloff), kicks a damped camera spring (pulses stack), side-chain ducks an AudioKit bus with auto-release, and dips blur/color correction only when DeviceBench quality clears PostQuality — low-end devices keep the shake and duck, never the post-processing cost; pure intensityAt/springStep. See Impact
  • Gait: procedural foot planting and body attitude — each leg down-casts from a look-ahead point (effector + velocity * LookAhead) and drives an AnimKit Reach IK onto the hit (airborne legs fade back to the animation), the root joint Transform carries a smoothed body-height offset over uneven footing plus a velocity-derived bank/pitch tilt (the dragon leans into the pivot before the track shifts); all client cosmetic through Motor6D Transform slots and IK targets, one PreRender stepper for every bound gait; pure tiltFor/heightOffset. See Gait
  • Projectiles: authoritative curved paths — a per-definition Path(seed, age, origin, velocity, flightSeconds?) → Vector3 evaluated identically on both machines: the server sweeps each tick’s Path(t-dt) → Path(t) segment for walls and capsule hits (the hitbox follows the curve exactly, no more straight hit line under an arcing visual), clients render the same function and bank along its tangent. Deterministic from spawn-packet data (serial seed, origin, velocity, flight seconds) — zero extra wire bytes; keep the function in one shared module for both defs. PathOffset remains cosmetic-only for flourish that must not move hits. See Projectiles
  • Hazards: dynamic procedural zones with a client mirror — annulus bands (inner/outer radius + height) evaluated as distance checks in a cadenced sweep, no phantom parts: radii as functions of age (expanding fire rings), anchors as fixed points or moving Instances (the band follows the caster and retires if the anchor dies), Duration auto-retire with everyone inside receiving Left before Retired, and occupancy tests each occupant’s last-position→position SEGMENT against the band so a sprint through a thin ring cannot tunnel between samples (a through-crossing fires Entered then Left in the same tick). Wire: CKHZ_Spawn(serial, defId, origin, seed, duration, anchor?) + CKHZ_Retire(serial); HazardClient renders pooled, seed-deterministic ring VFX from the same shared radius curves, following moving anchors, safety TTL past duration. Damage stays game-side: apply an Effect on Hazard.Entered, cleanse on Hazard.Left. Bus Hazard.Spawned/Entered/Left/Retired. See Hazards
  • ProjectileClient: OnImpact carries the wire serial (ported up from Withcraft&Wizardry) — OnImpact(position, velocity, serial): the serial is the same seed PathOffset receives, so impact cosmetics reproduce the exact per-shot roll the visual flew with; extra arg, old callbacks ignore it. See Projectiles
  • ProjectileClient: facing comes from authoritative velocity (ported up from Withcraft&Wizardry) — only curved rigs (Path/PathOffset) bank along their rendered motion; everything else orients on the true heading, so the muzzle-blend convergence no longer rolls and pitches wide visuals (blade slashes) for the whole flight of a short-range shot. See Projectiles
  • ProjectileClient: the cosmetic stack (ported up from Withcraft&Wizardry) — PathOffset(seed, age, velocity, flightSeconds?) per-frame curve hook, OnRender(visual, seed, age, flightSeconds?) per-frame visual hook, CosmeticOrigin(caster, origin) latency masking (the visual spawns at the muzzle THIS client currently sees and converges onto the true path, capped at 25 studs), OnImpact receives the impact velocity as a second argument (orient VFX to the surface the shot died on), and shots with known flight seconds park at their landing point instead of overshooting while the hit/expire packet is in flight. See Projectiles
  • Projectiles: per-shot flight control — the spawn packet carries flight seconds and the caster (cosmetic envelopes and latency masking read them), fire() takes optional maxDistance (point-targeted expiry: lifetime caps at maxDistance/speed) and speedMultiplier (per-shot speed with no wire change), OnExpire receives the expiry position and the expire packet carries it to clients. See Projectiles
  • Movement: AntiExploit.Exempt standing exemptionkernel.Bus:publish("AntiExploit.Exempt", player, true|false) for server-sanctioned free movers (broom riders, vehicles, scripted knockback); unlike repeated Forgive it never resets the Rewind buffer, so lag compensation keeps validating the rider’s shots. See Movement Monitor
  • Packet: paged name-to-id mappings + early-packet parking — channel ids ride paged Configuration children instead of RemoteEvent attributes (Roblox caps an instance’s total attribute payload at 1024 bytes; a real game’s channel count blew past it and errored channel definition), and payloads arriving before their id page park and replay when the map lands instead of erroring the receive loop. See Packet & Wire Types
  • NET window: whole-game traffic row — “Game total” shows this machine’s full network in/out KB/s (engine replication + physics + every remote) from the Stats service next to the kernel-channel Wire row. See Debug Panels
  • Diagnostics: per-role child containers — kernel diagnostics write short attribute names on ChloeKernelDiag<Role> Configuration children instead of ~29 long-prefixed attributes on ReplicatedStorage itself (the 1024-byte attribute cap errored the diag task in real games); hot tasks ride three 50-char-capped attributes; the debug panel translates legacy keys
  • Debug panels boot hidden — the F8 panel and SIM control start with their ScreenGuis disabled; F8 toggles them in. See Debug Panels
  • NetTap: Instance sized at zero bytes — instance refs ride the remote’s engine side array, not the kernel buffer; refusing to size them zeroed the wire counter for any Instance-bearing channel
  • WizardDuel template: casts complete — all three spells now declare ChannelSeconds and SpellKit defaults a missing value to 0 (instant) instead of erroring in beginCast; WizardDuel and GunArena clients call the intent handle’s fire(...) (the lowercase dot-call NetClient actually returns) instead of a nil :Fire method. See SpellKit, Starter Templates
  • Rewind: the claimed fire direction now gates hitsvalidateShot rejects claims whose origin-to-hit vector strays past DirectionDotMin (default 0.94, about 20 degrees) from ShotClaim.Direction with reason DirectionMismatch; previously the field was carried but never read. See Rewind
  • Studio boot gate: spec failures halt the play testMain.server asserts zero failed specs from both suites, matching the audit and fuzz gates instead of warning past red tests. See TestKit
  • LiveConfig: TTL expiry no longer splits the fleet — a poll that finds the document gone (MemoryStore TTL) reverts every override so long-lived servers agree with fresh ones booting into defaults; unreachable MemoryStore still holds current values. See LiveConfig
  • Analytics: playerless drops are counted and WatchErrors warns — the default LogCustomEvent destination counts skipped playerless entries in Stats.DroppedPlayerless, and attaching with WatchErrors plus the default destination warns that every ScriptError event would be dropped (they are playerless by construction). See Analytics
  • Settings: rejected client writes roll back — the kit’s validator pushes the authoritative value over Settings_Sync on rejection (unknown key or invalid value), so SettingsClient’s optimistic cache self-corrects instead of keeping the rejected value. See Player Settings
  • ShopKit: stock caps survive rejoins — per-window buy counters live in the profile when one exists (rotating windows persist until they roll, static-shop caps become per-player lifetime), stale window counters prune on the next buy. See ShopKit
  • InventoryKit: Intents = false and a MaxSlots wire guard — a second attach on the same kernel can skip INV_* channel wiring instead of crashing on redefinition, and MaxSlots asserts 1..255 at attach (slot indices ride NumberU8; larger bags would have client-unreachable slots). See InventoryKit
  • CompanionKit: leavers never orphan companions — session-leave cleanup moved out of the intent wiring, so Intents = false kits destroy a leaver’s companion too. See CompanionKit
  • README truth pass — removed the nonexistent Npc.Idle/Npc.IdleEnded topics, Npc.Tactic documents its three real roles, channel integer coercion documented as wrapping (replica fields clamp), Boolean1 marked channel-only (replica schemas use Boolean8), BonePhysics.attach() in every example, receipt ledger default corrected to 2000, rebind examples carry the device argument, Bad hearing blur corrected to 32 studs
  • ActorPool: fork-join over the worker poolpool:forkJoin(items, {ChunkSize?, Context?}) splits a batch into chunks (default 4 waves per actor) and every actor pulls the next chunk the moment it finishes its last, so uneven items self-balance across the pool; results stitch back in item order and the calling coroutine resumes once with (true, results) or (false, firstError). The worker fn runs as fn(item, context) per item; a chunk failure carries the absolute item index, stops new sends, and drains outstanding chunks. Compose phases by forking again over the joined results. dispatch() is unchanged. See ActorPool
  • TablePool: scratch table recyclingacquire()/release(t) recycle cleared Lua tables with their capacity intact (double release fails loud, idle stack caps at 256); Spatial grew allocation-free radiusInto/boxInto/coneInto variants that append into caller scratch, and the hot sweeps adopted them: NPCKit target-scan prefilters and Projectiles per-projectile candidate sets ride pooled scratch, NetGovernor per-player stats tables reuse in place. See TablePool
  • Mounts: map-authored attachment points — tag any instance CKMount, set a MountType attribute plus per-type attributes, and Mounts.attach(kernel, options) wires the systems at level load: Sound mounts become AudioKit playAt emitters (SoundName, Volume?, Speed?), Zone mounts feed Zones:addPart (ZoneName), BoneChain mounts bind BonePhysics chains (Damping?, Stiffness?, GravityScale?, WindScale?); built-ins mount only when their system is passed, so server and client attach off the same tagged map. Handlers extend and override with custom types returning cleanup closures; tag add/remove signals mount and unmount live (StreamingEnabled maps mount what streams in); handler failures warn and stay contained. Bus Mount.Added/Mount.Removed. See Mounts
  • Spatial: flat spatial-hash index — pointer-free grid over ids at world positions (packed integer cell keys, exact-filtered queries, coordinates clamp at ±524k studs, same-cell moves are position writes): radius/box/cone (horizontal FOV dot) /nearest (expanding rings), plus Spatial.characters(kernel) for a self-refreshing player-root index. Wired consumers: Projectiles.attach { Spatial } replaces the per-tick full-roster scan with a per-projectile radius query (fresh roots for the selected candidates keep hit math exact), NPCKit.attach { Spatial } refreshes the index in update() and broad-phases target scans through a stale-padded radius before any sight test. See Spatial
  • NetGovernor: adaptive per-client replication — grades every player Good/Strained/Poor from ping median + jitter (mean absolute deviation over a rolling window), unreliable packet loss (sequence-numbered echo probes over NG_Probe/NG_Ack, held until the client’s NG_Ready handshake so join silence never reads as loss), and the DeviceBench quality hint (NG_Device, clamped, floors at Strained, never Poor by itself); the worst axis wins. ReplicaService sends deltas to Strained clients every 2nd tick and Poor every 4th with per-player phase stagger — skipped fields bank per subscriber and merge into the next on-cadence send, so discrete state never goes missing and snapshots/removals always send; unreliable state channels grew { Priority = "Low"|"High" } and Low traffic sheds to Poor links before the wire (probe channel is High: shedding probes would lock the tier down). Shed counts in Governor.Stats; bus Net.TierChanged(player, tier, stats); client half GovernorClient.attach(). See NetGovernor
  • Forensics: anti-exploit flight recorder — every player’s kinematics roll through a ring ([t, position, look, velocity, humanoidState] at RecordHz 10 for WindowSeconds 20); a watched bus topic (default AntiExploit.MovementViolation) freezes the window into a capture and records TailSeconds more; re-flags during the tail append reasons to the same capture and leavers finalize immediately. Delivery: function destination or { Url } JSON POST (never blocks the sweep, failures fall into the in-memory ring, last MaxCaptures readable via captures()); export() emits the JSON a web tool can rebuild the run from; Forensics.replay(capture) walks an interpolated ghost rig through the movement in Studio. Captures hold movement, reasons, and the user id only. Bus Forensics.Captured. See Forensics
  • HairKit: automatic hair rigging over EditableMesh — boneless catalog hair gains a generated skeleton at runtime: plan() partitions mesh vertices into radial sectors x depth bands and emits a bone chain per sector (default 10 bones) with every vertex weighted to at most MaxInfluences bones (default 2, clamped to 4, weights normalized; root-plane vertices bind the root and never pull band centroids); rig() injects the skeleton (AddBone with ParentId/Virtual, SetVertexBones, SetVertexBoneWeights), registers the content into the DataModel (CreateDataModelContentAsync(Content.fromObject(mesh))), bakes through CreateMeshPartAsync, swaps the hardware-skinned result into the MeshPart in place, and creates the Bone instance skeleton the mesh links to by name (write-once, read-many — the baked editable stays rooted for the part lifetime and is never mutated again). Sway rides BonePhysics (its single stepper, camera-distance culling, DeviceBench MaxChains budget). attach() = client bulk manager (no per-accessory scripts); HairKit.server() rigs server-side so the baked bones replicate to every client once — zero per-frame network traffic in either mode. Two permission gates verified live: the “Allow Mesh & Image APIs” experience setting (supported() probes it with a real AddBone call) and per-asset read permission (CreateEditableMeshAsync loads experience-owned or creator-owned meshes only; third-party catalog hair returns (false, reason)); either failure keeps the bindAccessory pendulum fallback. Bus Hair.Rigged. See HairKit
  • Dissolve: vertex-sampled voxelizer with noise-advected dissolve, restructure, and particle morphs — MeshParts sample EditableMesh vertex positions (surface-shell fallback with Ball/Cylinder silhouette filtering — interior cells drop so nothing dissolves as a filled box; permission denials cached per asset so avatar effects warn once per session), home positions ride the target’s anchor part so reassembly, restructure, and morph endpoints follow moving targets (a player who walks off mid-effect reforms where they are), points snap to a DotSize voxel grid with per-cell dedup, and dots advect through a 3D Perlin field (ScatterSpeed, NoiseScale, Drift) while fading; Reverse assembles instead; Duration = nil returns a manual controller whose setProgress(0..1) erodes the surface noise-thresholded per dot, and dots above the threshold ease back to their surface cell and resolidify (ReturnSpeed) so a receding driver restructures the target (proximity wards that knit shut). morph(source, destination) voxelizes source and flies every dot into destination’s sampled shape — bottom-up pairing (pairPoints, every destination cell used), staggered launches (Stagger), Perlin turbulence arcs (Turbulence), source-to-destination color lerp — then reveals destination and keeps source hidden; avatars are first-class targets (Decals and Textures hide with their parts): disintegrations, character-to-statue morphs, reforming sources via Reverse plays; chained effects restore the pre-effect transparency, never a mid-chain hidden value. Chunks = true (play and morph) clones every source part as a full-detail flying chunk — meshes, textures, faces, and SurfaceAppearance survive, joints/welds/sounds/emitters strip — riding the same threshold, advection, or morph flight while shrinking into the dust, which keeps original avatar detail where denied-mesh grid sampling would box it; chunks destroy on finish. One Heartbeat stepper across every active effect, pooled dots positioned through workspace:BulkMoveTo, MaxPoints scaling 500..5000 by DeviceBench quality when unconfigured, CustomDot templates, Spin. Local-only VFX; broadcast the trigger for shared playback. See Dissolve
  • Starter templates — the repo’s templates/ directory ships three complete minimal games as reference code (GunArena: WeaponKit + Rewind + TeamKit + RoundKit + Leaderstats + global boards; WizardDuel: SpellKit + MeleeKit + MoveKit + curse/ward Effects; Obby: CheckpointKit + air steps + kill-brick Zones + ascending speedrun board). One server module and one client module each; linted in CI alongside src. See Starter templates
  • Studio boot gates in Main.server — every Studio play test now asserts zero High-severity securityAudit findings after start and runs Fuzz.run against every registered channel when the first session joins; handler errors fail the play test loudly. See Fuzz & audit
  • StreamingEnabled stance documented — from a module audit: server-authoritative systems are unaffected; client-side rules for BonePhysics binds and long-lived model references. See StreamingEnabled
  • API consistency: kernel-integrating modules use attach()InputDriver.attach(kernel) and MoveKit.attach(kernel, options) are the canonical constructors; .new remains on both as a deprecated alias. Plain object constructors (Pool.new, NetClient.new) and service definitions (WeaponKit.service) keep their shapes: the rule is attach = wires into a kernel, new = plain object, service = definition for registerService
  • PartyKit: same-server player parties — invites with TTL expiry (sweep-cleaned), one party per player (accepting leaves the current party), lazy creation (the party forms on the first accept with the inviter as leader), leader kick/promote, leader inheritance on leave (longest-standing member), disband at zero members, session-end cleanup of membership and pending invites both ways. Six fail-closed intents (PT_Invite/Accept/Decline/Leave/Kick/Promote, UserIds as F64 on the wire), PT_Sync roster pushes and PT_Invited notifications, Party.CanInvite hook (fail-open). Parties never touch the combat gates; sameParty and members (group-queue feed for Matchmaking) are the integration surface. Bus Party.Created/Disbanded/Joined/Left/Kicked/Invited/Promoted. See PartyKit
  • Effects is now a full status systemTickSeconds + OnTick periodic ticks (poison, regen) with catch-up bounded to 8 per sweep; Category dispel groups with cleanse(session, category?) (no category = every categorized effect; uncategorized effects never cleanse); Immune = {categories} on an effect blocks covered applies while it holds (wards are effects; apply returns 0 and publishes Effects.Blocked); owners receive a CKEffects state push after every change ({[name] = {Stacks, Remaining?}}) for status-icon UI. Bus adds Effects.Blocked(player, name, byEffect) and Effects.Cleansed(player, category?, count). See Effects
  • LootKit: weighted loot tables with persisted pity — entries yield items (Count fixed or {min, max}), nested tables (depth capped at 8, references validated at attach), or nothing; PityAfter = N force-hits on the Nth consecutive miss with counters persisted per player in profile Data.LootPity (rollFor/award advance pity, plain roll never does); award(session, table) grants through InventoryKit and returns (granted, overflow). Bus Loot.Rolled/Awarded. See LootKit
  • CraftingKit: recipes over InventoryKit — inputs check atomically before any take; Seconds > 0 queues one timed craft per session with cancel() refunds; Station recipes gate on the caller’s claimed station with proximity verified in the fail-open Craft.CanCraft hook; output that does not fit publishes Craft.Overflow; fail-closed intents CR_Craft {recipeId, station} / CR_Cancel. Bus Craft.Started/Completed/Cancelled/Overflow. See CraftingKit
  • ShopKit: soft-currency shops over InventoryKit — currency is an adapter ({Get, Take, Give}; default reads a profile field, CurrencyField = "Coins"); rotating stock derives from Seed + floor(clock / Every) so every server lists identical stock with zero coordination; Stock = N caps buys per player per rotation window; buys grant first and charge for what fit (partial grant = partial price, full inventory = no charge); fail-closed intents SH_Buy/SH_Sell with counts capped at 99. Bus Shop.Bought/Sold/Rejected. See ShopKit
  • CompanionKit: owned NPC followers — familiars/pets from NPCKit archetypes: one per session (re-summon replaces), a follow loop that walks at FollowDistance and pivots beside the owner past TeleportDistance, death cleanup with Companion.Died, and Aura holding an Effects buff on the owner while summoned; ownership gates in the fail-open Companion.CanSummon hook; fail-closed intents CP_Summon/CP_Dismiss. Bus Companion.Summoned/Dismissed/Died. See CompanionKit
  • Fuzz: hostile-payload sweep over every registered channel — the runtime companion to securityAudit(): per-schema-type hostile pools (empty/10KB strings, format specifiers, control bytes, 0, -1, type maxima, NaN, infinities, NaN vector components, nil/nested tables for Any) drive each intent and request through the real hook chain + handler pipeline; payloads that pass validation and crash the handler collect in Report.HandlerErrors (assert it empty as a boot gate); validator throws count as rejects; deterministic per Seed; Driver injectable for specs. See Fuzz & audit
  • InventoryKit: the item model — definition registry + slotted per-player bags: stacking (grants top up piles before opening slots), equip slots with displacement and OnEquip/OnUnequip, OnUse consumables (return true to burn one; draining a stack auto-unequips), move drag-and-drop (swap or merge, equip references follow the item). State persists through the session profile (Persist = false for session-only battle-royale bags). Grants/takes are server-API-only — no client message mints items; clients ride four fail-closed intents (INV_Move/Equip/Unequip/Use) validated against the server’s books, and the owner gets an INV_Sync state push after every mutation so inventory UI is pure rendering. Hooks Inventory.CanEquip/CanUse (fail-open); Bus Inventory.Granted/Taken/Equipped/Unequipped/Used/Changed. See InventoryKit
  • Leaderboards: global top-N on OrderedDataStores — queued submits flush on a cadence (newest value per key per window = one write no matter how fast scores change), KeepBest boards only overwrite improvements through atomic UpdateAsync (Ascending flips the comparison for speedruns), failed writes stay queued and retry next window, and top() serves a cached page (one GetSortedAsync per CacheSeconds regardless of readers; failed refreshes keep serving the previous page). The backend is a first-class seam — Roblox OrderedDataStores by default, custom stores as equals: implement declarative submit(board, key, value, keepBest, ascending) (the keep-best rule arrives as data, so an external DB applies it atomically its own way) or DataStore-shaped set(board, key, updater), plus sorted; contract asserted at attach. WritesPerFlush (default 30) caps each window so provider budgets (ODS limits, HTTP rate caps) survive submit spikes — overflow keeps its queue spot. NaN/inf submissions refuse; Bus Leaderboard.Updated(name, entries). See Leaderboards
  • TeamKit: teams wired into the combat gates — capacity-weighted auto-balance on session start, assign/teamOf/sameTeam/players, optional Roblox Teams mirroring for player-list colors, and map-authored spawn ownership (tag parts TeamSpawn + Team attribute; characters pivot to an owned spawn with the anti-exploit pardoned). Friendly fire off by default, enforced where damage happens: vetoes registered on Weapon.CanDamage (new fail-open hook in WeaponKit’s damage path) and Melee.CanHit; npc handles resolve through Archetype.Faction, so faction NPCs and same-named teams read as teammates. Bus Team.Assigned(player, team, previous?). See TeamKit
  • WeaponKit: Weapon.CanDamage hook point — fail-open gate fired before a validated shot deals damage ({Session, Attacker, Victim, WeaponId, Weapon}); TeamKit plugs friendly-fire policy in here, games add safe zones and duel rules without forking the kit. See WeaponKit
  • Repo-wide comment rewrite — every comment states contracts, constraints, units, defaults, and data shapes; rhetorical framing removed; option comments normalized to -- <fact>. Default <value>; ~680 comments across 118 files with the code proven byte-identical by a comment-stripping diff; three stale factual claims corrected against the code (Net pipeline order, ServiceManager cycle behavior, AnimKit IK helper class)
  • MeleeKit: server-authoritative hand-to-hand with parries — attacks are windup / hit-frame / recovery timelines on the SERVER clock; combos cancel recovery along declared ComboNext chains (Melee.Combo counts the string); blocking chips (BlockScale), guard-breaks shatter blocks (full damage, guard drops), and timed parries beat both: a parry press arms a ParryWindow (+LatencySlack so wire travel doesn’t eat honest frames) that kills the incoming swing and staggers the attacker (StaggerSeconds, x1.5 for parried guard-breaks — the triangle: parry > guard-break > block > attack > parry). Attempts consume ParryCooldown hit or miss, so spam can’t blanket a swing. Hit frames sweep range + facing arc with a fail-open Melee.CanHit hook for friendly-fire policy. Players ride auto-wired fail-closed intents (ML_Attack/ML_Block/ML_Parry — “attack while staggered” dies in validation); combatants are entity-agnostic (sessions, npc handles, models), and npc victims with no manual parry roll their own skill-scaled npc:defend() — difficulty-driven NPC parries for free. Bus: Melee.Hit/Blocked/GuardBroken/Parried/Staggered/Combo/State. See MeleeKit
  • MoveKit: abilities bound to movement context — “jump pressed while airborne” as a first-class trigger: abilities are a trigger (Input = "Jump" rides UserInputService.JumpRequest, other strings ride InputDriver Input.<Action> bus events, or kit:trigger(name)) plus a context gate (Grounded/Airborne/Rising/Falling/custom predicate over {State, AirTime, Rising, UseIndex, ...}), a charge pool that refills on landing, a cooldown, and a takeoff debounce (MinAirTime). Recipes: doubleJump({Power}) (fresh scaled jump mid-air) and airStep({Steps, Boost, OnStep}) (anime footholds — kills the fall, hops off the invisible platform, OnStep(position, useIndex) draws the glow pad). Ownership guarantee intact: the CLIENT executes (it owns its character’s physics), MoveKit.server meters through the fail-closed CKMove intent with its OWN charge/cooldown books and its OWN ground probe (a client that lies about landing gets no fresh charges); Forgive = true pardons the next anti-exploit sample for horizontal bursts. Bus: Move.Triggered (client), Move.Used/Move.Rejected (server). See MoveKit
  • ProjectileClient fairness rule: no invisible shotsMaxVisuals now caps only the FULL cosmetic visual; overflow spawns render as minimal pooled neon tracers instead of being skipped, and any shot whose flight path passes within ThreatRadius (default 15 studs) of the local character renders FULL regardless of the cap — an incoming round the target can’t see is a fairness bug, not an optimization. MaxTracers (default 300) bounds pathological spam (threatening shots bypass every cap). New pure ProjectileClient.threatens(origin, velocity, position, radius) + spec. See Projectiles
  • DeviceBench: granular per-axis device benchmark, computed quality budgets, and a 60fps governor — three time-boxed Luau workloads (compute, allocation churn, coroutine resumes) measure how fast the client actually runs, and each becomes a CONTINUOUS 0.25..4 axis multiplier against a mid-range reference, decimals fully allowed; the geometric mean is the overall Quality. Budgets are FUNDED PER AXIS — the workload a device is good at pays for the systems that stress it: strong Churn buys ParticleBudget/VfxDensity, strong Compute buys BoneChains/BoneDistance/ProjectileVisuals, strong Resume buys AudioChannels — so a device that benched 3.4x on churn but 1.2x on compute gets near-max particles while bone chains stay modest. profile() computes the budgets from a Result, any exact quality number (profile(1.35)), or a tier name; Low/Medium/High/Ultra tiers remain coarse conveniences for pick(). Client modules consume the profile when unconfigured: BonePhysics cull MaxDistance and NEW MaxChains chain budget (past it the chains nearest the camera win, the rest freeze at their pose), AudioKit MaxChannels, ProjectileClient NEW MaxVisuals (pooled-render cap; hits still land past it). Every default is an overridable option and every cap is purely cosmetic. scale(min, max) is log2-mapped over the band — every doubling of device quality buys the same slice of the knob. The GOVERNOR holds a stable frame target at runtime — 60fps first, beauty second: it watches p95 frame times and nudges the effective quality ×0.8 after ~3s of sustained misses, ×1.15 back after ~10s of clear headroom — granular nudges, not tier cliffs — capped at the benched quality (frame luck is not capability), with axis ratios preserved while dialing (a churn-strong device dialed down still favors its particles); changes arrive via OnChange(quality, profile) and Device.QualityChanged. Time-boxed means weak devices run fewer iterations in the same ~90ms instead of hitching longer; results cache, report Platform, optionally sample a median FrameMs, publish Device.Benchmarked, and auto-run on first helper use — pay the bench during loading (pairs with Preload: asset warming is network-bound, the bench uses the idle CPU). A client measurement — cosmetics only, never authority. Reference constants are provisional pending a live calibration pass. See DeviceBench
  • NPCKit is now HELPERS, not an AI runtime — the behavior system is gone: no priority-list {When, Step} runner, no kit-owned tick loop, no built-in behavior factories (wander/idle/chase/investigate/hide/cover/strafe/attack/suppress/flank/flee/tree all deleted). The kernel ships syscalls, not game rules — and chase/cover/flank policy IS game rules. What remains is a thin spawn/despawn lifecycle (model clone, health, died-cleanup, zones tracking) plus per-npc state, and YOUR loop composes the helpers: npc:updateTarget (acquisition/sight memory/graded detection), npc:canSee/npc:sweep (cone-gated eyes, pie-slice fans), npc:aimAt (the full skill model), npc:act (movesets), npc:defend, npc:moveTowards/npc:faceTowards, kit:findCover/kit:findPeekPoint (cover SCORING without the phase machine — Direction-attribute nodes, BackAway ground-giving, squad spread all kept), kit:all() (the roster your loop walks), and kit:update() (squad director + due melee windups, called from your tick). Two new standalone helper modules: Senses — hearing with levels of ATTUNEMENT (Keen/Sharp/Average/Dull/Oblivious or any custom {Range, BlurStuds}), anisotropic blur (direction resolves well, distance poorly), and a Distance override so route-measured occlusion plugs in; plus inCone/canSee vision gates — NPCKit’s own ears and eyes delegate here, so kit npcs and hand-rolled agents perceive identically. Pathfinding.follower — per-agent re-pathing/waypoint-following as a handle your loop steps (follower:step(position, goal) -> nextPoint, {Path?, Jump, Stuck}): goal-drift/grid-version re-paths, failed-search backoff, stuck watchdog, jump hints; npc:moveTowards is now a thin wrapper over it and applies the point via Humanoid/injected Move. BehaviorTree stays as the pure strategic helper — build one per npc, tick it from your loop with the kit clock. See NPCKit, Senses, Pathfinding
  • NPCKit trained-combatant pass: graded detection, pain response, ammo discipline, honest gunfights — sight can now charge instead of snapping: DetectionSeconds on updateTarget turns acquisition into an accumulator (fills faster up close and against fast movers, decays unseen; half charge publishes Npc.Suspicious and points the hearing memory at the glimpse so your search logic walks over for a look, full charge acquires), and re-sighting a target after real occlusion re-imposes HALF the reaction time — the reappearance is a fresh stimulus, not a free instant re-engage. Getting hit registers: npc:notifyDamage(attacker?) (called automatically by kit Hitscan/Melee on npc victims, wired to Humanoid damage on spawned rigs, published as Npc.Damaged) suppresses aim for 1.5s (read State.SuppressedUntil to duck an exposed peek) and drops an unseen attacker’s position into the hearing memory — a back full of bullets now turns the body around. The aim model grew up: leading uses the TRUE intercept solve (|delta+v·t| = s·t) instead of distance/speed, error is correlated drift + near-Gaussian jitter (bursts walk readably instead of statistically bracketing the target), continuous time-on-target tightens the cone, and suppression blooms it. Movesets: Magazine/ReloadSeconds force the reload pause players push into (Npc.Reload), melee WindupSeconds telegraphs the swing clock-driven with a real dodge window (Npc.Windup; landing re-checks the arc), and ranged actions in a squad HOLD FIRE when a squadmate blocks the lane (FriendlyFire = true opts out). npc:faceTowards tracks targets with the body while standing still — a stopped npc’s frozen vision cone would otherwise lose a target circling behind it. The squad director fields real fireteams — nearest holds Pressure, the rest alternate Flank (sides split so the pincer closes from BOTH directions, Squad.FlankSigns) and Suppress (base of fire) — and the blackboard expires 30s-stale intel so dead men stop being suppressed and departed Players stop being retained
  • Pathfinding: feet-vs-photons split, one-flood hearing, off-mesh links, unstickable movers — grid line of sight split into two honest queries: lineOfSight (walkability) now also gates CONSECUTIVE cells along the line by MaxStepHeight, so a Theta* shortcut between two in-band endpoints can no longer walk an agent off a sheer cliff a stepwise search would refuse, while new sightLine (vision — now backing NPCKit.hasLineOfSight on grids) lets eyes cross valleys and pits that block feet and only occludes on ground RISING past the band. New Pathfinding:distanceField({Grid, Origin, MaxDistance}) Dijkstra-floods route distances from one origin — emitSound Path occlusion now runs ONE search per sound instead of one full A* per listener. grid:addLink(fromWorld, toWorld, cost?) authors jump/vault edges (ledges, windows) that A*/Theta* traverse but never Theta*-shortcut across; humanoid movers jump for waypoints rising past step reach. Movement robustness: failed searches back off 1s instead of re-burning the full expansion budget every tick against an unreachable goal, Grid.Version (bumped by refresh/refreshRegion) invalidates cached npc routes when the map moves under them, a stuck watchdog (no ground covered for a second while trying) forces a repath plus an unstick hop, and PathMethod = "Roblox" computes off-tick — ComputeAsync yields could previously stall the entire npc population from one navmesh brain. Also: any-angle search uses the euclidean heuristic (octile overestimates euclidean segment costs — inadmissible), the ground raycast sets RespectCanCollide so foliage decor stops rasterizing as floor, and waypoint arrival radius scales with cell size. The default rasterizer stopped reading transient BODIES as geometry — a cell probed while a player or npc stood on it cached as blocked (or as walkable ground at head height) forever, including the asker’s OWN start cell — 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). New grid:nearestWalkable(position, maxCells?) — the forgiving front door for user-driven queries: clamps off-grid points into bounds and snaps clicks on top of walls to the nearest standable cell, ring by ring, instead of failing on GoalOutsideGrid/GoalBlocked technicalities. The clearance check also grew a NARROW PHASE: GetPartBoundsInBox matches BOUNDING boxes, and a wedge ramp’s bounding box is a full-height slab that blocked every cell of its own slope — non-block shapes (wedges, cylinders, meshes, unions) now confirm against real geometry with footprint-sampled down-rays, so ramps rasterize as the walkable slopes they are. See Pathfinding
  • BehaviorTree: reactive preemption + honest clocks — new ReactiveSelector re-evaluates from the FIRST child every tick so a higher-priority branch coming alive preempts a Running lower one (whose resume memory resets) — the right root for combat brains, where “enemy appeared” must interrupt “investigate” mid-plan; plain Selector keeps the resume-directly semantics. tick() threads an optional clock into Cooldown nodes (pass the NPCKit kit clock for determinism under spec, without the global _setClock swap), and reset() is the documented move on target change — a plan mid-flight against the old target is a bug against the new one. See BehaviorTree
  • NPC determinism + correctness touch-upsdefend() 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.Topics can safely list Npc.Hitscan so NPC gunfire alerts OTHER npcs without the shooter investigating itself
  • NPCKit tactical layer: squads, directors, and behavior trees — archetypes take Faction (faction-mates never target each other) and Squad (auto-join a shared blackboard): members report what their senses ACTUALLY produced (sight fixes exact, heard positions blurred by the listener’s skill) via squad:report, readable through squad:lastKnown(target) / npc:lastIntel(target); the per-squad director (stepped by kit:update()) allocates roles per engaged target, publishing Npc.Tactic(npc, role) for client animation polish — your loop reads squad:role(npc) to decide who pins the last-known fix and who curves the arc. Cover is map-authorable: tag parts (SpotTag) with an optional Direction attribute so a node only counts when the threat is on its covered side, and squadmates already camping a node push its score down. npc:sweep(direction, arc?, rays?, range?) slices the pie — a horizontal raycast fan across the unseen angle before the body commits. The vision cone gate is a dot-vs-cos compare that runs before every sight raycast. New Kits/BehaviorTree: Selector/Sequence composites with real Running-resume memory, Condition/Action leaves, Invert/Succeed/Cooldown decorators, reset(), injectable clock — build one per npc so plans never share memory, tick it from your loop
  • NPCKit combat brain: vision cones, sight memory, defense reactions, cover scoring — sight is a cone, not a sphere: new FieldOfViewDegrees difficulty knob (Perfect 360 → Bad 100) gates npc:canSee(target, maxRange?, fov?) against the rig’s actual facing before any raycast (hearing stays omnidirectional, covering the rear); RequireSight targeting tracks briefly-occluded targets for MemorySeconds then hands the LAST SEEN position to the hearing memory — your loop hunts the spot like a soldier instead of pathing omnisciently through walls. kit:findCover (with BackAway for fighters that give ground) breaks the target’s line of sight from grid cells or tagged nodes; kit:findPeekPoint finds the corner beside a spot with eyes on the threat. Defense: per-archetype Reactions (Against attack types, per-reaction Difficulty, cooldown consumed on ATTEMPT) roll DefenseSkill — kit Hitscan/Melee actions roll the victim’s reactions automatically when it’s an npc, npc:defend(context) exposes the same gauntlet to game code, and Npc.Defense(npc, name, success, context) publishes deflects AND fumbles. Per-action Difficulty overrides resolve at define-time (Perfect defense on a Novice trigger finger); anisotropic hearing blur stretches error along the sound direction (direction resolves well, distance poorly)
  • Ragdoll v2: rig-agnostic, build-once, owner-client replication — smooth down, clean recovery — NEW-format character rigs (AnimationConstraint joints, zero Motor6Ds) ragdoll by cutting the animated joints and TUNING the rig’s own ball sockets for the ride (friction via FrictionTorque (default 60) + limits, everything restored on release) — frictionless free-spinning joints were the first “spazzing ragdoll”; classic rigs keep the Motor6D→limited-socket swap, now also friction-damped. The residual spazz had two more causes, both fixed: the owner client now LATCHES the Physics state (auto-getup disabled + a StateChanged re-force — ground impacts knocked the state machine into Landed/GettingUp, whose standing forces wrestle the sockets) and limbs ride an auto-registered SELF-BLIND collision group (CKRagdollLimbs: jointed-pair no-collides missed hand-vs-hip and arm-vs-leg contacts, whose depenetration impulses shook the pile). No automatic force at all: a socketed stack loses its balance the moment the joints cut and crumples on its own — explicit Impulse hit flings distribute per part, since with rigid joints cut every limb is its own assembly and impulses on the massless ghost root moved nothing. Default FrictionTorque relaxed 60 → 10 (the high value was masking the state-machine and self-collision spazz, both now fixed structurally), and joint cones widened to anatomical ranges — shoulders 120, hips 90, elbows/knees 80 (tight cones read as a stiff mannequin; limits only stop the impossible). New Options.Limits merges per-joint {Upper, Twist} overrides over the built-in table, so the flop is tunable per game. The ROOT joint itself is never cut: the HumanoidRootPart is the camera subject, and cutting its joint orphaned a massless, non-colliding ghost that fell through the world — the camera dove after it, and release “recovered” the character under the map; left attached, the root rides the torso and the camera lies down with the body. Recovery uprights the root +2 studs (it lies at torso height; standing needs leg room or GettingUp unfolds into the floor). Components build ONCE per model and toggle on re-ragdolls (no instance churn on the wire). Player characters: humanoid state is client-authoritative, so the server pushes the CKRagdoll state channel and ChloeKernel/Net/RagdollClient.listen(netClient) answers on the owning machine; release WAITS for the server’s joint re-enables to replicate (standing up while joints were still cut was the broken recovery), kills linear AND angular momentum, stands the root upright with yaw kept, re-enables Animate fresh, and gets up — server-simulated rigs get the same momentum-kill + upright on their release. PlatformStand applies only to server-simulated rigs — stacking it on a player character made the two controllers fight. Verified live: state holds Physics the whole ride, torso angular velocity settles to zero within a second of the kick, recovery stands upright (UpVector.Y = 1.00) straight into Running. See Ragdoll
  • Pathfinding: partial grid refreshgrid:refreshRegion(minWorld, maxWorld) drops only the cells a moving wall or opening gate swept; the rest of the map keeps its raster, so continuously-moving courses re-path cheaply (the full refresh() stays for map-wide changes)
  • BonePhysics: boneless accessory sway + ambient windbindAccessory (run automatically by bindCharacter) makes accessories WITHOUT bones move: the accessory becomes a swing-clamped pendulum hinged at its joint, steered through whichever joint kind wears it — Weld/Motor6D handle-side offsets, RigidConstraint handle-side Attachment CFrames (the modern catalog-hair case; nothing swapped or disabled), or WeldConstraints swapped for an equivalent local Weld and restored exactly on release. MaxSwingDegrees keeps hair drifting instead of flipping. New AmbientWind = Vector3 sim option: a procedural breeze gusted by layered sines, no Wind function needed. Verified live on a real avatar: 10/10 accessories bound, hair swaying through its untouched RigidConstraint. See BonePhysics
  • NPCKit: opt-in NPC-vs-NPCArchetype.Targetable = true joins the default target roster so other npcs hunt that kind through ordinary acquisition (self-targeting excluded, faction-mates filtered); override GetTargets for full roster control
  • AudioKit: the kernel’s all-in-one audio engine — bank-registered 2D/3D playback over auto-created bus SoundGroups (Music/SFX/Ambient/Voice/UI), with every fade, crossfade, and duck driven by ONE scheduler stepper (no TweenService churn, no per-sound Heartbeat connections, deterministic under spec). Sound instances and 3D emitter parts pool; channel pressure evicts lowest-priority-first (music is never evicted); ducks stack with deepest-wins semantics. Adaptive music mixes layered stems by WEIGHT on one locked timeline (playLayers + setWeights/resync); cue points fire loop-aware Audio.Cue bus events; speak() queues dialogue on the Voice bus, auto-ducks, and publishes Audio.Subtitle/SubtitleEnded for the game’s UI; reverb zones apply by listener position with priority overlap; soundscapes scatter randomized chirps over an ambient bed. Kernel-only integrations: Alert rings NPCKit ears so player-audible sounds are also NPC intel, Occlusion = "Path" muffles by the pathfinding route around geometry (walls block, corridors carry), bindSettings persists bus volumes through the validated Settings service, and AudioKit.server/audio:listen broadcast plays over one state channel. See AudioKit
  • AnimKit: the animation counterpart — bank-registered clips played through per-rig controllers with track caching, crossfading exclusive Groups (walk -> run without the T-pose blink), marker-to-bus forwarding (Anim.Marker), and an inverse-kinematics layer: rig:ik() wraps IKControl (LookAt/Reach/Transform/Rotation) with stepper-faded weights, Vector3 targets ride a movable helper, Properties passes raw overrides, and release() hands the skeleton back. Rigs die with their models; assetIds() feeds the preloader. See AnimKit
  • Preload: the framework’s asset pool — collects every registered asset (AudioKit/AnimKit banks via assetIds(), plus extra ids and whole Instances), dedupes, and warms them in batched PreloadAsync calls off-thread, narrating Preload.Started/Progress/Done on the bus for loading screens; await() blocks a boot flow until warm; failures collect and report, never abort. See Preload
  • BonePhysics: a lean, leak-free SmartBone replacement (ChloeKernel/BonePhysics, client-oriented shared module) — verlet chains over Bone hierarchies with a FIXED-timestep solver (hitch-clamped substeps; no frame-rate-dependent droop), flat arrays instead of per-bone objects, camera-distance culling that snaps back to the pose on wake, and a teleport guard so respawns don’t whip chains across the map. Poses write through Bone.Transform (the animation slot) composed against the parent’s effective world — never WorldCFrame, whose setter permanently mutates the bone’s rest CFrame (verified empirically; that mutation is exactly the “SmartBone broke my skeleton” class of bug). Lifecycle is airtight: roots auto-unbind on Destroying, handle:destroy() restores Transforms, sim:destroy() releases everything. bindCharacter scans accessories/layered clothing for boned parts; bindParts drives plain part chains (stud tails) with the same solver. Settings per bind: Damping/Stiffness/GravityScale/WindScale; sim options include injectable Wind and ShouldSimulate
  • Ragdoll: Motor6D-to-BallSocket ragdolls with per-joint swing/twist limits (R6 + R15 names), collision flips (limbs drape, root goes ghost + massless), optional death wiring, Impulse flings, Duration auto-release, and a MaxActive cap that releases the oldest pile-up. release() restores EVERYTHING it touched — motors, collision flags, collision groups, AutoRotate, RequiresNeck, humanoid state. Replication is near-perfect at zero extra cost: the joint swap is server-side and the engine streams the physics; a player’s own character keeps its network ownership (per the kernel’s no-ownership guarantee), so their ragdoll simulates locally at full rate. Bus: Ragdoll.Started/Ended(model)
  • NPCKit combat model: hitscan, arcs, live difficultyKind = "Hitscan" actions resolve through an injectable raycast with aim error only (no lead — it’s instant) and publish Npc.Hitscan(npc, origin, hitPosition, victim?) tracers; Action.Gravity runs a ballistic low-arc solve for lobbed projectiles (out-of-range lobs 45 degrees); npc:setDifficulty(nameOrTable) swaps the skill model mid-fight
  • NPCKit routing skill + path events — new PathSkill difficulty knob (Perfect 1.0 → Bad 0.2): high skill takes any-angle Theta* and re-paths eagerly as goals drift, low skill runs plain A* and clings to stale paths; explicit kit PathMethod still overrides. Every computed route publishes Npc.Path(npc, waypoints) so visualization/debug tooling never monkeypatches the finder
  • NPCKit senses: hearingemitSound(position, {Range?, Loudness?, Source?}) alerts NPCs with range AND accuracy scaled by difficulty (new HearingMultiplier/HearingBlurStuds knobs on every preset: Perfect pinpoints the exact position, Bad hears a vague 30-stud blur — the ear model itself lives in the standalone Senses module), Sounds.Topics auto-emits from listed bus topics at the acting player’s position ({["Weapon.Fired"] = 60} = gunshots make noise with zero wiring), and Sounds.Occlusion picks the geometry model: "Through" (walls ignored, default), "Blocked" (walls stop sound — the “through-wall stuff off” switch), or "Path" (distance measured AROUND obstacles on the grid, maze-correct; no route = not heard). The heard fix lands in State.HeardPosition/HeardAt/HeardSource for your loop’s investigate logic; a fresh sound resets the search scratch fields. Bus: Npc.Heard(npc, heardPosition, source?)
  • Pathfinding: terrain- and height-aware grids — the default rasterizer now raycasts for ground (Terrain, parts, and model geometry all count) and requires the agent column above it clear; cells carry their ground Y, so waypoints follow elevation, adjacent rises past MaxStepHeight (default 4) read as unclimbable cliffs in A*/Theta* expansion, and grid line of sight refuses to cross walkable high ground — a wall top still occludes even though you could stand on it. Injected IsWalkable may return a second ground-Y value to opt in; plain boolean rasterizers behave exactly as before
  • NPCKit: server-side npc handles — archetypes carry a skill model with seven difficulty presets (Perfect aimbot through Bad, or any custom {AimErrorDegrees, ReactionSeconds, LeadSkill, CooldownMultiplier} table, overridable per spawn) driving aim spray, reaction delay, moving-target lead, and attack pace, and movesets UNIFIED with player systems: Projectile actions fire the same kernel Projectiles definitions players shoot (npc handle rides as the owner session), Custom actions receive a pre-aimed direction for pointing at existing WeaponKit/SpellKit server logic, Melee gates on range for creature attacks. Your game’s loop drives the handles (kit:all(), npc:updateTarget, npc:act, npc:moveTowards re-pathing through Pathfinding as goals move); spawned models auto-track in Zones (Zone.EntityEntered/Left). Bus: Npc.Spawned/Died/Action
  • Pathfinding: one handler, four methods, each lazily required on first use — AStar (8-connected grid, octile heuristic, corner-cut safe), ThetaStar (any-angle: parents shortcut through line of sight for straight segments), Direct (single LoS test; grid raster or world raycast), Roblox (PathfindingService navmesh; yields). Grids rasterize a world rectangle lazily with caching (refresh() after map changes), take injected IsWalkable for RTS maps/dungeons/deterministic specs, and searches carry a MaxExpansions budget with reasoned failures (GoalBlocked, NoPath, BudgetExhausted)
  • QuestKit: quests as data — objectives count server-published bus topics ({Topic, Count?, Match?, Filter?}), so there is no client input to validate. Default rule counts events whose args include the session/player; Match pins an arg (zone name, weapon id); Filter takes over for actor-less topics. AutoAssign, Repeatable (resets fresh on completion), OnComplete rewards, progress persisted via profile with transient/persisted merge on load. Bus: Quest.Progress(player, questId, index, count, required), Quest.Completed(player, questId). See QuestKit
  • InteractionKit: exploit-proof ProximityPrompts — prompts are created from CollectionService tags (and follow tag changes live), but a trigger is client input, so every one re-validates distance (latency slack), server-side line of sight, and per-player cooldown through the fail-closed Intent.Interact chain (kit gate at 50; game rules prepend) before OnInteract runs. Bus: Interact.Triggered(session, id, instance), Interact.Rejected(player, id). See InteractionKit
  • Settings: persisted player preferences behind an allowlisted schema — client writes ride a rate-limited intent through the fail-closed chain where unknown keys reject, numbers clamp, strings cap, and string arrays REBUILD clean so hidden hash keys never reach storage; values persist via the DataDriver profile (player-chosen transient values win the merge), Settings_Sync pushes authoritative values back, and SettingsClient gives the client fetch/optimistic-set/onChanged over the same channels (wire to InputDriver rebind for persistent remaps). Bus: Settings.Changed(session, key, value). See Player settings
  • Analytics: batched, sampled, rate-capped event pipeline — track() is a queue insert, a background flush hands batches to AnalyticsService:LogCustomEvent (funnels/retention with zero infrastructure) or an injected destination (webhook/warehouse); per-event sampling and a per-minute cap protect the custom-event budget with every drop counted, never silent; optional WatchErrors bridges Kernel.ScriptError. Crashing destinations lose only their own batch. See Analytics
  • Kernel:securityAudit(): one pre-ship sweep over the whole security model — flags handler-bearing channels with no validator (High), gate hook points flipped FailOpen where a crashing validator would PASS payloads (High), Open = true escape hatches to re-confirm against hostile input (Medium), and channels still on the default rate limit (Info); prints a severity-sorted summary and returns findings for CI-style assert(#findings == 0) gates. HookRegistry gained listPoints(). See Fuzz & audit
  • Zone-scoped replica interest: replica:bindZone(zones, zoneName) — subscription flips the instant Zone.Entered/Left fires (full snapshot on entry, remove on exit, quantize baselines flushed) instead of waiting for the replica’s interest scan, which stays wired underneath via isInside so pre-bind occupants converge. Players outside a region pay zero bytes for its replicas. See Replica
  • Boot readout floors at 0.01ms — a sub-hundredth-millisecond boot no longer prints a suspicious “0ms”
  • Zones: entity tracking, per-zone signals, multi-part volumes, tag wiring — zones now detect NPCs/props/vehicles alongside players: track(entity)/untrack (returns an untrack fn) and trackTag(tag) (CollectionService, live; untag or Destroy fires the leaves) put entities in the sweep, riding new Zone.EntityEntered/EntityLeft bus topics while Zone.Entered/Left stay players-only. add() now returns a per-zone handle carrying Entered/Left Signals plus OnEnter/OnLeave callback options, so handlers stop name-filtering the global topics; session-end and remove() leaves flow through the same dispatch (handles fire everywhere). One zone can span several parts (add(name, { PartA, PartB }), addPart), and addTagged(tag) builds zones straight from Studio tags — zone name from the ZoneName attribute (fallback: part Name), same-named parts unioning into one zone. New reads: entitiesIn(name), isInside(name, occupant). Injected Query doubles and the default overlap-query reuse are unchanged. See Zones
  • Built-in hook point & bus topic catalog — a new API-reference section documenting every hook point the framework fires (context fields, fail-open vs fail-closed vs observational, when it runs) and every bus topic it publishes (args, publisher), plus the consumed AntiExploit.Forgive pardon topic and the hooks-vs-bus mental model (hooks ask permission, topics announce facts). Zones cookbook and API rows rewritten for the new surface — on this site: 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 (Owner option), waitForGeneration surfacing GenerationError, and a public validate() for wiring client intents through the kit’s rules. Generators stay server-side by design: the peer that changes a parameter generates and the results replicate, so ServerScriptService generators are never decompilable. Ships an ExampleGenerator demonstrating params:Pause() timeout safety and Seed-derived determinism. Bus: Procedural.Spawned/Updated/Rejected/Removed. See ProceduralKit
  • Engine diagnostics that survive production: new Diag{Role}ReplKbpsIn/Out, PhysKbpsIn/Out, Primitives, MovingPrimitives, Contacts attributes plus a panel Engine section (replication bandwidth, physics replication, awake primitives, contacts) — none gated by memory tracking, so live servers keep a full engine debugging surface. Investigated enabling engine memory tracking by default: impossible — Roblox turns it off in all production builds before any code runs and Stats.MemoryTrackingEnabled is READ-ONLY with no opt-in (staff-confirmed); ALL memory getters (incl. GetTotalMemoryUsageMb) return 0 + warn when off. The panel now states this plainly where the memory bars would be instead of silently omitting rows
  • Frame-aware scheduler budgeting (server default): each step budgets the slack left under a 16.5ms frame target after everything else’s measured share of the frame — Stats.HeartbeatTimeMs (the engine’s smoothed heartbeat-phase compute, idle excluded, verified empirically) plus Stats.PhysicsStepTimeMs, with the kernel’s own smoothed share subtracted back out so its own work never shrinks its own budget. An idle server drains ~16ms of backlog per frame; an engine-heavy one backs off automatically, never below the 1.5ms BudgetSeconds floor; a frame-interval guard drops to the floor when frames run >25% late (covers unmeasured costs like replication serialization, and misreporting platforms). Explicit BudgetSeconds keeps the old fixed-slice behavior; TargetFrameSeconds opts any scheduler in; clients default to fixed 1.5ms (the render thread is invisible to the frame stat); phase schedulers stay fixed so a second frame-aware scheduler cannot double-claim the same slack. See Scheduler
  • Frame composition diagnostics — the frame decomposed by WHO is spending it: Scheduler.Stats records the live budget, heartbeat-phase busy time, and physics time every step, plus a windowed step avg/max/deferred drained by takeStepWindow(); new Diag{Role}FrameBusyMs/PhysicsMs/FrameTargetMs/StepMaxMs attributes; both panel windows grew a Frame section splitting measured compute into engine + other scripts / physics / kernel share (client adds render CPU) with a frame-compute bar against the target and per-row diagnosis tooltips
  • Overload simulation mode: ~20ms of scheduled burn demand per frame (8 tasks every step on a Low/Background mix) plus 500 entities — deliberately exceeds the whole frame target so the budget pegs, Deferred goes nonzero, queues back up, and the red panel states show (verified live: 74-143 deferrals/window, frame compute ~18ms). HighIntensity reframed as the realistic load a modern server clears green in ~1.2ms/frame — that being visible is the point. See Simulation
  • Simulation harness (Simulation.server.luau + ChloeKernelServer/Simulation): three load modes selected by the ChloeKernelSimulation ReplicatedStorage attribute — HighIntensity (shooter-scale stress: 200 entity processes, hook/bus floods, replica delta churn, CPU burn), SmallGame (light baseline), Showcase (narrated 6-act feature tour incl. real haste/slow on present players and an anti-dupe replay demo). Hot-swappable mid-game, full teardown, Studio-gated, fake packet factories so simulated traffic never touches the real wire. See Simulation
  • GcWatch: GC statistics from gcinfo() sampling — allocation rate (the lever that drives Luau’s incremental GC cost; Roblox exposes no pause timings), GC cycle cadence, and reclaim sizes that tell leaks (heap climbs, reclaims ~0) apart from churn (heap stable, busy cycles). Auto-attached by enableDiagnostics (new Diag{Role}GcAllocKbS/GcCyclesMin/GcReclaimKb attributes), graded in both debug-panel windows, and reported in the simulation heartbeat. See GcWatch
  • SimPanel + SimLoad (Debug/): a SIM CONTROL window that switches server simulation modes live (Studio-only ChloeKernelSimControl remote, mode-whitelisted) and runs client-side loads on the local machine — SimLoad is the role-agnostic rig set built on shared primitives only (processes, hooks, bus, serde, pools, burn) with its own GC-reporting heartbeat. Wired through the test place’s client Bootstrap; delete that block for a real game. See Debug panels
  • DataDriver backups (Config.Backup): rotated periodic copies to any second backend (DataStore {Name}_Backups by default; Memory/Http/custom for external DBs), optional per-save Mirror to a live slot, and load-time fallback that restores the newest readable backup when a record is corrupt or unmigratable (profile.RestoredFromBackup + Kernel.ProfileRestored bus event). Exactly-once per key per interval game-wide with zero coordination — backups ride the session-lock owner’s save path and the schedule lives inside the record, so no server count can duplicate them and server hops don’t reset the clock. Restore never bypasses session locks (corrupt ≠ unlocked) and never runs while the backend is down. New APIs: backupNow, listBackups, peekBackup (read-only, lock-free); Config.Clock injectable for tests. See 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 accepts Codec = "Auto" (inferred from Defaults, extras on). See Serde
  • Debug panel NET window + NetTap: a live wire inspector for kernel channel traffic from both machines — direction, channel, decoded args, payload bytes computed from schema widths, server processing ms / request round-trip ms, and verdicts (OK/Rejected/RateLimited/NoSession/Dropped incl. Packet flood drops and oversized unreliable sends). Capture is pausable (freeze the tail), clearable, and filterable (replica noise toggle); clicking a row pins it in an Inspector that shows the message both ways — decoded argument values AND the serialized payload as hex, generated through the same Packet type engine that writes the wire. Server entries reach the client on a debug-only mirror channel that never exists in production. SimPanel’s control path is now a real kernel intent, so mode clicks demonstrate the full pipeline in the window. ImGui gained a selectable row widget. See Debug panels
  • Panel limit bars (consumed vs permitted): both scheduler sections show a frame-budget bar (used / budget ms (%) — 100% is where tasks start deferring; budget published via new Diag{Role}BudgetMs attribute) and the server memory row reads against Roblox’s 6.25GB process cap; ImGui bars now support hover hints
  • Replica Quantize deadbands ({[field] = threshold}): writes that moved less than the threshold from the last replicated value update server data but skip the wire entirely — drift is bounded by the threshold, so slow movement still replicates once it accumulates. Sub-visible jitter on chatty fields (positions, progress bars) now costs zero bytes. The NET window’s replica rows also show per-recipient size (21B each x30) so fan-out totals don’t read as one large buffer
  • Packet ported into the kernel (ChloeKernel/Net/Packet): no more external package — the wire transport is now kernel-maintained, formatted to kernel standards, and bug-fixable in place. Attribution to Suphi Kaner (5uphi) retained in CREDITS.md; the Packages/ folder is gone and all requires point inside the kernel. See Packet & wire types
  • Serde fast engine (techniques from light/holy by @hardlyardi, MIT): schema and struct codecs now compile per-type writers that resolve byte widths at compile time, allocate once at the exact size, and run with zero capacity checks and no global-cursor swap — fixed-schema encode 463ns to 285ns (38% faster), decode 514ns to 367ns, dynamic schemas 22-27% faster, replication deltas at 270/293ns. Output is byte-identical to the wire-engine path (spec-asserted with cross-decoding); schemas using uncovered types (Any, CFrames, sequences, Extras) fall back automatically, so no stored record or wire format changes
  • NumberVlq wire type: variable-length unsigned integer (7-bit continuation, up to 2^53) — 1 byte under 128, 2 under 16384; usable in schemas, structs, and channels; clamps like the other integer types
  • selene std extension (buffer_bitops.yml) teaching selene 0.28 about buffer.readbits/writebits instead of file-level allowances
  • BusBridge fulfills the Bus’s advertised NetBridge: ServerTopics (wildcards) auto-forward to every client as ordinary bus publishes, Bus:publishRemote forwards any topic explicitly, and client publishes ride a rate-limited intent through exact-name whitelisting (fail-closed, no wildcards upward) plus the Intent.BusPublish hook chain, arriving as (topic, session, ...). See BusBridge
  • MessageDriver: cross-server events over MessagingService — Serde-packed + base64 payloads, a clear error at the ~1KB ceiling, publish retries with backoff, per-message crash isolation for subscribers. Completes the trio: MemoryDriver = state, DataDriver = storage, MessageDriver = events. See MessageDriver
  • Net Registry: single-source channel definitions consumed by both sides (Registry.define/server/client) — schemas can never drift; declarative Validate rules (Range/OneOf/MaxLength) compile into the fail-closed hook chains at priority 5; Net:onRequest added for late handler attachment. See Registry
  • Server log tail in the panel: server Logger lines mirror to the SERVER window on a debug-only channel (same gate/pattern as the NET mirror), giving both windows a live tail
  • SimLoad real-wire flood: client load modes fire the SimNoise kernel intent at rate (HighIntensity deliberately exceeds its 50/s limit so RateLimited verdicts demonstrate live in the NET window); NET byte estimates now cover NumberVlq
  • Kernel:shutdown(): full teardown — diagnostics, GcWatch, services, phase schedulers, the main scheduler, and the bus — and clears the boot singleton, so a fresh boot() on the same modules works (tests, in-place re-runs)
  • ChloeKernelDebugUserIds allowlist: comma-separated UserIds restricting the live-server debug surface per user — panel attach, the NET/log mirrors (now per-recipient sends, never FireAllClients), and the SimControl intent
  • Teardown across the board: ProjectileClient destroy() (+ client-side safety TTL and serial-wrap recycling), ReplicaClient unlisten()/destroy(), ImGui Window:destroy() (drag connection severed), Panel/NetPanel detach handles, Effects detach(), Zones destroy(), ErrorWatch destroy(), Matchmaking destroy now unsubscribes everything
  • LiveConfig override deletion: set(key, nil) removes the key from the shared document and every server reverts to its default on the next poll (previously a removed override stayed stale until restart). See LiveConfig
  • Player records persist Meta.UserIds, forwarded by DataStoreBackend to UpdateAsync as the metadata Roblox uses for GDPR erasure tracking
  • CI: rojo build smoke test and luau-lsp strict type analysis (non-blocking until the (self :: any) escape hatches are typed away); lint paths widened to all of src; leftover Hello.luau scaffolding removed
  • InputDriver multi-key bindings: Keyboard/Gamepad accept one Enum.KeyCode or an ARRAY of them, so one action can ride several physical inputs; rebind and getBindings snapshots mirror the shape. See InputDriver
  • Typed public surface for Studio autocomplete: ServerKernel fields carry concrete types (Scheduler/Hooks/Bus/Services/NetDriver instead of any), kernel:net() returns a typed NetDriver (the module loads at boot purely for its type — driver CONSTRUCTION stays lazy), session.Profile is typed as Data.Profile, and registerService/spawnProcess parameters are annotated
  • Intents/requests are now fail-closed on a MISSING validator, not just on a failing one. A channel with a Handler but zero validators rejects every payload unless explicitly marked { Open = true } — closing the footgun where an unguarded GrantCoins/SellItem intent let a client mint currency or set a stat at will (an absent validator used to mean “accept anything”). Declaring Validate rules counts as a validator; Open = true is the audited escape hatch for genuinely no-auth channels (payload-less toggles, idempotent ready-ups). Surfaced three ways: a one-time [NetDriver] ... fail-closed warn naming the channel, an Unguarded verdict in the NET panel, and Net:auditValidators() for a proactive post-boot sweep. Registry channel defs gained Open too. Existing kit/Registry channels are unaffected (they all register validators); only handler-bearing channels you never guarded change behavior — in the safe direction, visibly, in dev
  • The whole tree type-checks clean (1116 → 0 luau-lsp diagnostics) and the CI analysis step is now BLOCKING: Studio’s script analysis shows zero squiggles across the framework. Fixes were annotation-only — named option-type locals for the options or {} union trap, ImGui.Ui-typed render callbacks, :: { any } on the deliberately heterogeneous Static/Enums templates, expression-level casts where the old solver loses refinements. Spec files moved to --!nonstrict (their fakes are deliberately partial doubles; the framework itself stays strict). luau-lsp pin corrected to a real release (1.68.0)
  • NET inspector shows per-argument wire types: pinning a row now lists each argument’s wire type, and "Any" arguments expand to the adaptive engine’s actual best-fit pick (Any:U8, Any:F32, Any:Table{Wave:U8}, …) — verify at a glance that values ride the optimal width. Mirrored server entries carry it too (NetTap.describeWire is public)
  • Rewind’s MaxFutureSeconds default raised 50ms → 200ms: real GetServerTimeNow skew plus frame timing on rough connections could push honest stamps past 50ms and reject as FutureTimestamp; a future stamp only clamps to the newest sample, so the looser default costs nothing. See Rewind
  • Live servers no longer flood the console with “Memory tracking is currently disabled” warnings: the panel’s Total/Script-memory rows and the diagnostics MemoryMb attribute now check Stats.MemoryTrackingEnabled first (live servers run with tracking off; Studio always tracks) — the panel shows a dim explanatory row instead and the SERVER window simply omits the bar
  • Hitbox visualization (ShowHitboxes attach option / lag:showHitboxes(enabled)): every castRay draws the rewound capsules it tested — green for the hit, red for tested-and-missed — as short-lived server parts every client sees. Visual only, zero cost when off. Rewind.drawHitbox is public for game tooling
  • Projectiles capsule mode: setting Hitbox = {Radius?, HalfHeight?} on a projectile definition switches PLAYER hit resolution from raw character-geometry raycasts to the same capsule hitboxes the hitscan path uses (consistent per-weapon forgiveness knobs), and the landing step draws the tested capsules through the attached Rewind’s ShowHitboxes display. Definitions without Hitbox keep the legacy behavior exactly; in capsule mode OnHit receives { Position, Instance } instead of an engine RaycastResult
  • Lag-compensated hit DETECTION with per-weapon hitboxes: Rewind:castRay(shooter, origin, direction, range, timestamp, hitbox?) casts a shot against every tracked player’s REWOUND capsule (pure Rewind.rayCapsule underneath, spec-verified) — previously only the CLAIM was lag-compensated while the actual raycast ran at live positions, so high-ping shots missed strafing targets the shooter had dead-on. WeaponKit uses it whenever config.Rewind is attached, with per-weapon Hitbox = { Radius?, HalfHeight? } sizes (default 1.6/2.4); world geometry still blocks at its current state — walls don’t rewind, and live character bodies never block shots aimed at where targets USED to be
  • Decode paths hardened against hostile bytes (wire and storage): length prefixes are bounds-checked against the remaining payload BEFORE any allocation — a 5-byte payload with a crafted U32 prefix could previously force a multi-GB buffer.create on the server. Adaptive table decode is depth-capped (48), unknown packet/adaptive-type/enum/static ids error with clear messages instead of nil-indexing, VLQ reads cap at 8 continuation bytes, and the client receive loop is pcall-isolated like the server’s
  • BusBridge reflection closed: a topic whitelisted in ClientTopics that also matched a ServerTopics wildcard reflected the server Session object (plus attacker-chosen args) to every client; client-originated publishes never auto-forward down anymore
  • HttpBackend treated 404 as success on every method — a misconfigured BaseUrl or changed route made every save report OK while writing nothing; 404 now only means “new key” on GET, and PUTs carry If-Match when the API returns an ETag so a concurrent writer loses the CAS instead of last-write-wins
  • A player leaving during a slow profile load orphaned the profile forever (the session teardown had already run, bind() threw, autosave kept the lock fresh, and the player was kicked on every rejoin until the server died); the load now releases immediately when the session ended mid-flight
  • Autosave ran inline in the scheduler step, stalling every other kernel task (projectiles, sweeps, zones) for the duration of DataStore round-trips including retry ladders; it now runs off-thread, paces saves across a quarter of the interval, and never overlaps itself. DataDriver.new asserts LockTtlSeconds >= 2x AutosaveSeconds — the autosave is the lock heartbeat
  • One in-flight save per profile: autosave, save(), and release() could interleave retry ladders, landing an older snapshot after a newer one or re-locking a key the release just unlocked; release deactivation now happens inside the mutex
  • A failed final save was dropped silently; it now retries in the background (5/15/30s) before giving up loudly, and releaseAll bails before BindToClose’s 30s hard kill with a warn naming every unsaved key
  • Schema downgrades refuse to load: a rolled-back deploy no longer runs old code over newer-schema records — and the refusal is non-recoverable, so it can never trigger a backup restore over newer data
  • Receipts could acknowledge an unsaved grant: a re-invocation during the save window hit the ledger check and returned PurchaseGranted with zero persistence (a crash then lost a paid grant); acknowledgement now requires a landed save on every path, grant handlers roll back partial mutations on error, and the dedupe ledger default grew 50 → 200
  • Transactions: replay ids persist in Data — written atomically with the trade itself — so DuplicateTransaction protection survives rejoin/server hop; a throwing save() can no longer leak both InFlight locks
  • findUnserializable rejects sparse arrays and non-integer numeric keys (they do not survive a JSON round-trip); MemoryDriver’s buffer probe no longer condemns the driver to base64 on a transient error, and getRange reports a corrupt entry instead of throwing
  • Process suspend/resume race double-stepped forever: resuming before the queued step drained scheduled a second copy and both chains re-enqueued themselves; a StepQueued flag keeps exactly one chain. Self-kill no longer abandons the suspended thread unclosed or double-finishes as Completed; the PID registry is weak-valued so dropped processes collect
  • Scheduler recurring tasks no longer backlog under budget starvation — a starved 0.1s task could queue ~10 copies that ran back-to-back the moment budget freed, exactly the burst the snap-forward comment claimed to prevent
  • Predicted-intent acks ride AFTER the handler with error isolation: “accepted” now means “applied” — a throwing handler acks a rollback instead of leaving the client’s optimistic effect diverged from a server that changed nothing. Request handlers pcall to RejectValue, and a timed-out invoke resumes with RejectValue instead of nil (one failure encoding)
  • Rate-limit buckets keyed to departed Players could be recreated after session cleanup (Packet dispatches deferred); the session check now precedes bucket creation
  • Registry.predict() builds one handle per channel per client — repeated calls previously shared the ack channel while running separate sequence spaces, so an ack for one handle resolved (or rolled back) the wrong handle’s entry
  • MessageDriver’s 1KB guard measures the base64 payload — capping raw bytes passed 769-950-byte messages the service then deterministically rejected after burning the whole retry ladder
  • Replica interest joins converge every view: pending sub-threshold quantize drift flushes to existing subscribers when a snapshot is taken (the documented “threshold is the maximum drift” bound previously degraded to ~2x for joiners), same-tick joiners skip the duplicate delta, and no-op scalar set()s stay off the wire entirely
  • HookRegistry fires over a snapshot — a handler unregistering itself mid-fire shifted the live array and silently SKIPPED the next handler; in a fail-closed validation chain that is the wrong failure mode
  • ErrorWatch requires Logger relatively (a cloned kernel silently shared the original’s Logger), prunes its dedup table (interpolated error messages each minted a permanent key), and stores its connections
  • Signal:destroy() resumes wait()-parked threads instead of leaking them suspended forever; Pool:release() errors on a double release (two acquires could share one Instance); expect().toEqual survives cyclic tables; a throwing InputDriver handler no longer suppresses the action’s Bus publish; Bus stops caching matchless dynamic topics and clears its wildcard cache on destroy; Logger sink dispatch reuses one thread instead of allocating a closure + coroutine per log line per sink
  • String/Buffer length prefixes are enforced in both engines — a 300-byte value previously wrapped its U8 prefix to 44 and silently desynced the whole stream (documented as “max 255”, never checked)
  • Adaptive fractional numbers take the F32 form only when it round-trips exactlydecode(encode(0.1)) now returns 0.1 (F64), keeping the “smallest width that holds the value” promise honest
  • Static1/2/3 error on unregistered values both ways instead of writing index 0 and decoding to a silent nil; EnumItem errors clearly on unknown types/values; UDim/UDim2 Scale clamps at the S16 field’s range instead of overflowing; the Packet factory errors past 255 channels (U8 wire ids would silently collide); the shared-cursor no-yield invariant is documented at both engines
  • Matchmaking dequeues tombstone their queue entries (MemoryStore queues can’t remove by value): coordinators on every server filter reads against the tombstone map, consume poisoned reads, and re-queue live members — no more undersized matches built around ghosts. Match teleports retry with backoff and re-queue stranded members, publishing Matchmaking.TeleportFailed
  • SoftShutdown covered no reserved servers: Matchmaking match servers matched the transit-server check (reserved, owner 0) and got NO BindToClose protection; the bounce decision is per-player join data now, every reserved server gets shutdown protection, and bounce/transit teleports retry
  • Movement’s teleport threshold prorates with the sample gap — a server hitch no longer reads a fast-but-legit runner crossing 80 studs over several missed sweeps as a teleport; rubberbands preserve the character’s facing
  • Rewind buffers reset on respawn and on AntiExploit.Forgive — a buffer spanning death → spawn interpolated between the two and rejected honest post-respawn shots as OriginMismatch. WeaponKit anchors the muzzle to the shooter’s REWOUND position (ping x move speed no longer eats the 8-stud origin tolerance) and consumes the fire-rate window only when the whole validator chain passes
  • CheckpointKit no longer races the async profile load: the stage field initializes lazily per touch instead of eagerly at join (where it pinned session data and crashed the validator once the profile attached without the field); progress earned during the load window merges into the profile on Kernel.ProfileLoaded
  • Effects state is per-instance — the Simulation showcase attached a second Effects to the real kernel and its sweep removed (and its recompute clobbered) the game’s own effects; ActorPool surfaces worker require failures (CKDead attribute + clear dispatch error) instead of burning a full timeout per dispatch forever, and guards dispatch-after-destroy
  • Stale-click delivery across relabeled buttons: widget slots are reused by call order, so a dynamic button list could hand a pending click to a DIFFERENT logical button on the next render (mode buttons relabel exactly like this). Buttons now drop pending clicks when their label changes
  • Projectiles capsule mode hoists per-tick work out of the projectile loop: one world-pass exclude list and one alive-target snapshot per step, shared by every projectile (was a rebuild plus a FindFirstChild sweep per player per projectile per tick), and the default raycast reuses a single RaycastParams, reassigning FilterDescendantsInstances only when the list identity changes
  • Process steps stopped allocating a queue entry + handle per resume: new Scheduler:scheduleEntry re-enqueues one caller-owned entry per process for its whole life (the handle was never used). A/B under HighIntensity: GC alloc 4.4MB/s -> 0.6MB/s (7x) with step times statistically identical
  • Bus:publish no longer allocates on unmatched topics (shared frozen empty match) and the wildcard match cache is bounded at 512 entries (reset-and-repopulate past the cap), so dynamic per-entity topics that match a broad wildcard cannot pin one cache entry each forever
  • Replicas of the same tick rate share ONE recurring scheduler entry — the scheduler walks every recurring entry every frame, so a hundred 10Hz replicas now cost one slot in that scan, not a hundred
  • Rewind samples cache the HumanoidRootPart per character (FindFirstChild on spawn/respawn, not SampleHz times a second per player); Zones sweeps build one character include list per sweep with one reused OverlapParams; Logger.emit early-outs before building the entry when the console is off and no sinks exist
  • Process stepping no longer allocates a closure per resume. Every yield re-scheduled via a fresh anonymous closure, which missed the profiler’s function-identity cache on every step (full debug.info + string cost, the same class the 0.2.0 profiler fix addressed) and fed the GC a closure per process per frame. One stable step closure per process now: under the HighIntensity simulation (200 entities + bolts) server step time dropped 1.33ms → 0.50ms and GC alloc fell ~20% (4.9 → 3.9MB/s)
  • Hot tasks attribute process time to the process, not the kernel. New Scheduler.setTaskOrigin(fn, label); Process registers each step closure as Process <name>, so the panel now reads Process SimBolt 19.8ms/s instead of lumping everything into Process:89 — which also revealed the real cost driver in the sim (bolt Part.CFrame writes, 3× the cost of 200 pure-math entities)
  • License: private access. ChloeKernel access is granted individually by Chloe and is revocable; granted users may not share the source or grant access on her behalf. Game-use grants, required CryptedChloes attribution, the no-redistribution rule, and the vendored Packet exclusion are unchanged. Public docs live in ChloeKernel-Docs; the docs being public grants no framework access
  • README rewritten in reference-documentation tone: introduction/pitch phrasing removed, stale spec counts fixed, and examples reworked to demonstrate features per config field (neutral names, annotated gates) instead of telling game stories — the intent example now shows schema/rate-limit/validator-chain/handler explicitly, and the kit examples document what each field enforces
  • Deferred was invisible during overload: TasksDeferred was reset at step start and published by a task running INSIDE a step, so it structurally read zero exactly when overload happened (and under hard saturation the Background-priority diagnostics task stopped running entirely). Counts now finalize at step end (readers always see the last completed step) and SUM into the diagnostics window; the diagnostics publisher and Kernel.Overload streak moved to Kernel priority — health reporting must survive the very overload that starves Background work
  • Server step diagnostics no longer phase-lock: Diag{Role}StepMs published a single last-step point sample; at a 1s diagnostics cadence against 0.1s recurring work the sample aligned with the same frame type for a whole session (reading ~35% of budget one boot and ~180% the next with nothing changed). Now a windowed average plus the new StepMaxMs
  • Vendored Packet (marked mod #5): the server flush loop is pcall-isolated — a FireClient that throws (a player disconnecting inside the PlayerRemoving cleanup window) killed the while-true flush thread and silently stopped EVERY outgoing packet for the rest of the server’s life
  • Effects/Zones sweeps collect first, fire after: expiry removes and enter/leave publishes ran synchronously mid-iteration, so a subscriber that re-applied an effect (or added/removed zones) inserted keys into the very tables being iterated — undefined behavior in Luau iteration
  • Transactions commit in place, preserving table identity: profile.Data was swapped to a new table on commit, silently orphaning any reference game code held to it (or any cached subtable); commits are now deep in-place merges, so every identity survives the trade, and rollback restores from a pre-commit deep backup
  • Prediction acks task.cancel their timeout thread instead of letting it idle out the window; ReplicaClient pending never-listened replicas are capped at 64 with oldest-first eviction and a warn
  • Logger/ErrorWatch specs disabled the Logger console sink and never restored it, silencing ALL console logging for the rest of any Studio session that ran the boot suite (found by the simulation harness’s missing heartbeat)
  • Vendored Packet (marked mod #3): the Any engine’s string/buffer writes used a U8 length prefix, so values over 255 bytes wrapped the length byte and silently corrupted the stream. Long values now use new type ids 29/30 with a U32 length; short values keep the original framing (stock payloads decode unchanged)
  • Packet F16/F24 halved values at power-of-two boundaries (mod #4): the writers put the rounded mantissa straight into the bit field, so any value whose mantissa rounded up to a power of two (63.99, 127.99, 255.99 — observed as health values collapsing: 63.99 -> 32) overflowed the field to 0 with an unbumped exponent and decoded at HALF its value; Vector3F24 components were equally affected. The carry now bumps the exponent (63.99 encodes as 64), clamping at each format’s max. Sub-normal magnitudes (below 2^-14 F16 / 2^-30 F24), which previously wrote a negative exponent field, flush to zero

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
  • 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

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).