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.
The three folders
Section titled “The three folders”| 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.
What ships in the skeleton
Section titled “What ships in the skeleton”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.
Boot order
Section titled “Boot order”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:
- In Studio only, the spec gate runs the
SharedandServerframework suites, then the game suite fromServerScriptService.Game.Tests. A failure raises before the kernel boots, so the play session keeps running with the failures in Output and no kernel. ServerKernel.boot()builds the kernel, wires sessions and the close handler.Bootstrap.luauruns. It requiresGameand calls it with the kernel.Game/init.luaucallsRegistry.server(kernel, Channels), which defines every channel on the NetDriver and attaches each channel’sValidaterules as a priority 5 hook. It then setsService.Neton every module inRegistry.luauand registers each one.kernel:start()runs everyinit, then everystart, 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 notOpen.- In Studio, or when
ReplicatedStoragehas the attributeChloeKernelDebug = true, diagnostics start for the F8 panel. - 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:
- In Studio only, specs in the client’s
Game.Testsrun throughTestKit.runSpecs. Any failure raises<n> client game spec failure(s)before the kernel boots. Kernel.boot(), thenBootstrap.luau, which callsGame.Game/init.luaucallsRegistry.client(Channels), which builds aNetClientand constructs every State channel at once, then setsController.Netand registers every controller.kernel:start(), then the F8 debug panel attaches (Studio only unlessChloeKernelDebugis 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.
Game/init.luau and Registry.luau
Section titled “Game/init.luau and Registry.luau”The server entry is 18 lines:
--!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) endendThe 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.VoteMapin one service is the same handle as in another. Netis written onto the module table itself. The ServiceManager passes that same table asselftoinit,startandstop, soself.Netis there from the firstinit.- Channels exist before any system runs. A service can bind handlers in
startwithout 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.
Game specs on Play
Section titled “Game specs on Play”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.
What goes where
Section titled “What goes where”| 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 |
What exploiters can see
Section titled “What exploiters can see”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 everyRateLimitand everyValidaterule. AOneOflist of redeem codes in a channel definition publishes the codes. Rules that depend on secrets or server state belong in anIntent.<Name>hook on the server.src/Shared/Game/Config. Loot odds, drop tables, admin lists and anti-cheat thresholds go insrc/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.