Skip to content

The Game layer

Where game code lives in a ChloeKernel repository, how the Bootstraps load it between kernel boot and start, and what each side can see.

ChloeKernel is a framework and a workspace for building one game on top of it. The framework lives in src/Shared/ChloeKernel and src/Server/ChloeKernelServer. The game lives in three Game folders, one per side, and game work never edits the framework: it configures kits, subscribes to Bus topics and adds hooks. The Bootstraps load the Game folders after the kernel boots and before it starts, so every game system is registered and every channel is defined before the first init runs. Project layout maps the whole repository tree.

Folder on disk Studio destination Holds Who can read it
src/Shared/Game ReplicatedStorage.Game Channels.luau (every game channel), Config/ (tunables the client may see) Server, every client, every exploiter
src/Server/Game ServerScriptService.Game init.luau, Registry.luau, Services/, Components/, Config/ (server-only tunables, kit configs), Data/ (profile and product grants), Tests/ Server only
src/Client/Game StarterPlayer.StarterPlayerScripts.Game init.luau, Registry.luau, Controllers/, Screens/, Components/, Tests/ Every client, every exploiter

The destinations come from ChloeKernel.project.json, which maps src/Shared to ReplicatedStorage, src/Server to ServerScriptService and src/Client to StarterPlayerScripts. Two more folders hold documents: docs/game (the current game’s Design.md and Plan.md) and docs/agents (the catalog, recipes and templates, see Agent workflow).

The world, lighting, terrain, models and ScreenGuis are authored in Studio and saved in the place file. Nothing on disk describes them except the map requirements in docs/game/Plan.md.

A fresh checkout already has a working, empty game layer:

File Contents
src/Shared/Game/Channels.luau return Registry.define({}) with a comment showing the channel shape
src/Shared/Game/Config/GameConfig.luau A frozen table with Name = "ChloeKernel Game"
src/Server/Game/init.luau Builds the server channel handles and registers every service in Registry.luau
src/Server/Game/Registry.luau Requires ComponentService
src/Server/Game/Services/ComponentService.luau Mounts every module in Game/Components (see Components)
src/Client/Game/init.luau Builds the client channel handles and registers every controller in Registry.luau
src/Client/Game/Registry.luau Requires ComponentController and UIController
src/Client/Game/Controllers/ComponentController.luau The client half of the component system
src/Client/Game/Controllers/UIController.luau Binds screen modules to Studio-authored ScreenGuis (see Screens)

Tests/, Config/ on the server, Data/, Components/ and Screens/ do not exist until something creates them. Scaffolding creates most of them.

The two Main scripts are the script roots. They boot the kernel, call the Bootstrap, and start the kernel. Neither one holds game code.

On the server, src/Server/Main.server.luau runs these steps in order:

  1. In Studio only, the spec gate runs the Shared and Server framework suites, then the game suite from ServerScriptService.Game.Tests. A failure raises before the kernel boots, so the play session keeps running with the failures in Output and no kernel.
  2. ServerKernel.boot() builds the kernel, wires sessions and the close handler.
  3. Bootstrap.luau runs. It requires Game and calls it with the kernel.
  4. Game/init.luau calls Registry.server(kernel, Channels), which defines every channel on the NetDriver and attaches each channel’s Validate rules as a priority 5 hook. It then sets Service.Net on every module in Registry.luau and registers each one.
  5. kernel:start() runs every init, then every start, in dependency order, and prints [ChloeKernel 0.9.1] Server booted <n> services in <ms>ms. It then warns once for each client-to-server channel that has no validator and is not Open.
  6. In Studio, or when ReplicatedStorage has the attribute ChloeKernelDebug = true, diagnostics start for the F8 panel.
  7. In Studio, a deferred security audit fails the gate on any High finding (an unguarded channel is one). Outside Run mode, the first player’s session also triggers a fuzz run that drives hostile payloads through every channel’s validators (handlers stay off) and fails the gate on any validator crash.

On the client, src/Client/Main.client.luau is shorter:

  1. In Studio only, specs in the client’s Game.Tests run through TestKit.runSpecs. Any failure raises <n> client game spec failure(s) before the kernel boots.
  2. Kernel.boot(), then Bootstrap.luau, which calls Game.
  3. Game/init.luau calls Registry.client(Channels), which builds a NetClient and constructs every State channel at once, then sets Controller.Net and registers every controller.
  4. kernel:start(), then the F8 debug panel attaches (Studio only unless ChloeKernelDebug is true).

The client Bootstrap also attaches SimPanel, the framework test place’s simulation window. It gates itself to Studio and debug places. Delete those two lines for a real game.

The server entry is 18 lines:

src/Server/Game/init.luau
--!strict
-- Game server entry. Builds the network handles once, then registers every
-- service listed in Registry. src/Server/Bootstrap.luau calls this between
-- kernel boot and start.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Channels = require(ReplicatedStorage.Game.Channels)
local NetRegistry = require(ReplicatedStorage.ChloeKernel.Net.Registry)
local Services = require(script.Registry)
return function(kernel: any)
local Net = NetRegistry.server(kernel, Channels)
for _, Service in Services do
Service.Net = Net
kernel:registerService(Service)
end
end

The client entry is the same with NetRegistry.client(Channels) and Controllers. Three consequences follow from this shape:

  • One handle table serves every system on a side. self.Net.VoteMap in one service is the same handle as in another.
  • Net is written onto the module table itself. The ServiceManager passes that same table as self to init, start and stop, so self.Net is there from the first init.
  • Channels exist before any system runs. A service can bind handlers in start without waiting, and a client handle waits for the server’s definition when it first sends.

Registry.luau returns a list of system definitions: one require per module in Services/ (or Controllers/), plus kit service entries such as RoundKit.service(config). List order does not decide boot order. The ServiceManager sorts names alphabetically and walks each system’s Dependencies depth first, so the order is deterministic and follows the declared dependencies. Directive CK010 fails the check when a module in Services/ or Controllers/ is missing from the registry, is required twice, declares a Name that differs from its file name, or has a name that does not end in Service or Controller. Services and controllers covers the system contract.

Specs are modules named <Name>.spec.luau anywhere under Game/Tests on either side. They use TestKit and run on every Play in Studio before the kernel boots:

Side Location at runtime Runner On failure
Server ServerScriptService.Game.Tests The spec gate, as the Game suite after the framework suites The server kernel never boots; a plugin harness session ends with the report
Client Game.Tests beside the client Main script TestKit.runSpecs The client kernel never boots

A passing run prints only its summary lines. A test that provokes a Logger warning declares it with T.expectWarning(fragment); an undeclared warning fails the test. python tools/scaffold.py service and controller create a placeholder spec in Tests/ for each new system. The spec gate page covers the harness.

Adding Put it in
A network channel src/Shared/Game/Channels.luau (Channels)
A tunable the client may see (map names, prices in the game’s own currency) src/Shared/Game/Config
A Robux price shown to a player Not in code or config: Roblox Plus, regional pricing and price tests change it per player. Config holds the pass and product ids; the UI reads each price from the client Prices module created at boot (Pricing).
A tunable clients must not see (loot odds, anti-cheat thresholds, kit configs) src/Server/Game/Config
A server system with its own state and client requests src/Server/Game/Services, plus a Registry.luau entry
A client system (camera modes, placement, input modes) src/Client/Game/Controllers, plus a Registry.luau entry
Behavior on many tagged instances Components/ on the side that owns the behavior
A UI screen src/Client/Game/Screens, plus a ScreenGui of the same name in StarterGui
Profile defaults, migrations, product grants src/Server/Game/Data
Rules that can be pure functions A plain module next to its user, with a spec in Tests/
Folders, tagged parts, spawn points, UI trees The place file, listed in docs/game/Plan.md

Everything that replicates to a client can be read by an exploiter, and client code can be modified. That covers all of ReplicatedStorage.Game and all of StarterPlayerScripts.Game:

  • Channels.luau, including every RateLimit and every Validate rule. A OneOf list of redeem codes in a channel definition publishes the codes. Rules that depend on secrets or server state belong in an Intent.<Name> hook on the server.
  • src/Shared/Game/Config. Loot odds, drop tables, admin lists and anti-cheat thresholds go in src/Server/Game/Config.
  • Every controller, screen and client component. A client system presents and requests; the server decides.

Nothing in ServerScriptService.Game replicates. Directive CK009 fails the check when shared or client code references ServerScriptService or ServerStorage, so a client module cannot quietly depend on server-only config.

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