Skip to content

Kits Overview

The kernel contains no game rules. It ships scheduling, networking, sessions, hooks, the bus, and persistence — and deliberately stops there. Kits are where game rules live: pre-built userspace services in ChloeKernelServer/Kits that implement one genre mechanic each (hitscan guns, mana casting, melee parries, checkpoints, shops, parties) as server-validated gameplay over those primitives. You register a kit, configure it, and extend it through hooks — you do not fork it.

Every kit follows the same contract: the client sends requests, the server decides. A kit’s config is not a suggestion — each field maps to a server-enforced gate. FireRate = 8 in WeaponKit means the ninth shot in a second dies in validation no matter what the client claims.

No kit imports another. They compose through exactly two channels — a fail-open hook for “may this happen?” questions, and the bus for “this happened” announcements — and both channels work whether or not anything is listening. Trace one weapon hit through both:

Client fires → WK_Fire intent → Rewind validates the rewound-capsule shot
Weapon.CanDamage hook chain (fail-open)
── TeamKit registers a friendly-fire veto here ──
(WeaponKit has no idea TeamKit exists; TeamKit
never touches a line of WeaponKit's code)
Bus: "Weapon.Hit" / "Combat.Kill"
┌───────────────┬────────────────────┼────────────────────┬───────────────┐
▼ ▼ ▼ ▼ ▼
QuestKit AchievementKit Chronicle Your Bootstrap ...
(an objective (progresses or (records the event subscribes once:
quietly ticks unlocks a whether or not Impact.pulse(),
toward done) persistent anyone else is AudioKit.playAt(),
condition) listening — zero Effects.apply()
setup required) — one handler,
several systems

The same two-channel shape repeats everywhere: SpellKit delegates its payload to Projectiles or Effects instead of owning either; CompanionKit rides NPCKit archetypes and applies its Aura through Effects; a landed Sweeps hit or a triggered Hazard publishes the same way a weapon hit does. None of these kits hold a reference to each other — every arrow above is a hook registration or a bus subscription, both addressable by name ("Weapon.CanDamage", "Combat.Kill") with no shared import. Add a new reaction — a kill-cam CameraKit shot, a Chronicle trace filter, a Discord webhook — by subscribing to the topic that already exists; nothing upstream changes or even redeploys.

The Composition section below is the same idea at the level of specific kit pairs, if you want the concrete list instead of the general shape.

Config-driven kits are kernel services — build with Kit.service(config), register, done. Kits you interact with at runtime return a handle from Kit.attach(Kernel, options) with methods your code calls.

local Root = game:GetService("ServerScriptService").ChloeKernelServer
local WeaponKit = require(Root.Kits.WeaponKit)
local MeleeKit = require(Root.Kits.MeleeKit)
-- Shape 1: config-driven service. Registered, then driven by client intents.
Kernel:registerService(WeaponKit.service({
Weapons = {
[1] = { Name = "Rifle", Damage = 34, FireRate = 8, MaxRange = 300 },
},
}))
-- Shape 2: attached handle. Client intents auto-wire AND your server code
-- (NPC behaviors, quest scripts) calls the same methods players ride.
local Melee = MeleeKit.attach(Kernel, {
Moveset = {
Jab = { Damage = 8, ComboNext = { "Cross" } },
Cross = { Damage = 12 },
},
})

BehaviorTree is the one exception: a pure helper with no internal scheduling — you build a tree and tick it from your own loop. MoveKit is the other special case: it lives in Shared/ChloeKernel because it has two halves — attach() on the client executes abilities, server() on the server meters them.

Four patterns repeat across every kit. Learn them once and each kit page becomes a table of specifics.

Kits that accept client input define prefixed intent channels on the NetDriverWK_Fire, SK_Cast, ML_Attack, CKMove, INV_Use, SH_Buy. Each channel carries a fixed wire schema and a per-channel rate limit, and every message runs the fail-closed Intent.<Channel> hook chain before any handler executes. Fail-closed means rejection is the default: a validator returning false rejects, and a validator that errors rejects too. An exploiter cannot get a message through by crashing a check.

The kit’s own gate registers at priority 50. Your game rules prepend at lower priority numbers and run first — no forking:

-- "No firing in the lobby" — prepends below WeaponKit's gate (priority 50)
Kernel.Hooks:on("Intent.WK_Fire", function(context)
return Kernel:getService("RoundKit"):current() == "Round"
end, 10)

Where a kit asks a game-rules question — may this damage land, may this craft start, may this companion summon — it fires a fail-open hook point instead: Weapon.CanDamage, Melee.CanHit, Craft.CanCraft, Companion.CanSummon, Party.CanInvite, Inventory.CanEquip/Inventory.CanUse. Returning false vetoes; an erroring handler is skipped and the action proceeds.

The asymmetry is deliberate. An erroring intent validator must not let an exploit message through (fail closed). An erroring damage-policy handler must not silently break all combat in your game (fail open). Input trust and game policy fail in opposite directions.

TeamKit is the pattern’s showcase: FriendlyFire = false registers vetoes on Weapon.CanDamage and Melee.CanHit, and neither combat kit knows teams exist.

Every kit publishes its outcomes on the kernel busWeapon.Hit, Melee.Parried, Craft.Completed, Obby.Checkpoint. That is the extension surface for reactions: kill feeds, VFX replication, leaderboard submits, and QuestKit objectives all subscribe instead of patching kit code. Topics also compose kits that never import each other — QuestKit counts Combat.Kill and Zone.Entered without knowing what publishes them.

Kits that hold long-lived player state write it through the session profile when a DataDriver is attached — session.Profile.Data — and fall back to transient session.Data otherwise. CheckpointKit stages, QuestKit progress, InventoryKit bags, LootKit pity counters, and ShopKit’s default currency field all follow this rule. No kit talks to DataStores directly; the loss-hardening lives in one place.

Kits stack. The composition lines to know:

  • InventoryKit is the item substrate. LootKit awards its rolls through it, CraftingKit consumes inputs and grants outputs through it, and ShopKit sells out of it. Define items once in InventoryKit’s registry; the other three reference ids.
  • CompanionKit rides NPCKit. A companion is an NPCKit archetype spawned and follow-looped by CompanionKit; its optional Aura is an Effects name applied to the owner while summoned.
  • SpellKit delegates its payloads. The kit owns mana, cooldowns, and channels; what a spell does is your Cast function — typically firing Projectiles or applying Effects (SpellKit).
  • TeamKit vetoes into the combat gates and PartyKit deliberately does not — parties are social, not combat policy, and feed Matchmaking group queues instead.
  • BehaviorTree drives NPCKit handles. Trees make decisions; the handle’s helpers (and MeleeKit/Projectiles via movesets) execute them.
Kit Registration Client channels Purpose
WeaponKit service() WK_Fire Server-validated hitscan weapons: fire-rate/ammo gates, lag-compensated hits against rewound capsules
MeleeKit attach() ML_Attack / ML_Block / ML_Parry Server-authoritative melee: windup/hit-frame/recovery state machine, combos, the block/guard-break/parry triangle
SpellKit service() SK_Cast Mana, regen, cooldowns, and channelled casts as killable kernel Processes
MoveKit attach() + server() CKMove Movement-context abilities (double jump, air step, dash) — client executes, server meters
NPCKit attach() NPC lifecycle and per-npc state (skill model, senses memory, cooldowns); your loop makes the decisions
BehaviorTree helper Selector/Sequence/decorator trees with no internal scheduling — tick from your own loop
InventoryKit attach() INV_Move / INV_Equip / INV_Unequip / INV_Use Server-authoritative items: definition registry, slotted stacking bags, equip slots, use handlers
LootKit attach() Server-rolled weighted loot tables with nesting and per-player pity persisted in the profile
CraftingKit attach() CR_Craft / CR_Cancel Recipes over InventoryKit: atomic input checks, timed crafts with cancel refunds, station gating
ShopKit attach() SH_Buy / SH_Sell Soft-currency shops over InventoryKit with deterministic cross-server rotation and stock caps
CompanionKit attach() CP_Summon / CP_Dismiss Owned NPC followers built from NPCKit archetypes, with owner auras via Effects
QuestKit service() Quests as declarative objectives counted off server-published bus topics
RoundKit service() — (replica out) Round loop: ordered phase cycle, duration timers, MinPlayers gates, countdown replication
CheckpointKit service() — (server-side Touched) Validated obby progression: sequence and minimum-time checks, profile persistence
TeamKit attach() Team assignment, team spawns, and friendly-fire vetoes wired into the combat gates
PartyKit attach() PT_Invite / PT_Accept / PT_Decline / PT_Leave / PT_Kick / PT_Promote Same-server parties: invites with expiry, leadership flow, roster pushes
InteractionKit service() prompt triggers via Intent.Interact Exploit-proof ProximityPrompts: every trigger re-validates distance, line of sight, and cooldown server-side
ProceduralKit service() — (wire yours through service:validate) Runtime ProceduralModel spawning with fail-closed parameter validation and rate-limited regeneration

The starter templates are complete minimal games; each server file lists its map requirements at the top.

Template Kits exercised Alongside
GunArena WeaponKit, TeamKit, RoundKit Rewind lag compensation, Leaderboards
WizardDuel SpellKit, MeleeKit, MoveKit Effects curses and wards
Obby CheckpointKit, MoveKit Zones kill bricks, Leaderboards speedrun board