What a slice is
One feature's data, rules, UI view and server behaviour in one folder.
What it is not
Not layers shared by every feature: UI, managers, services, data, each touched by every change.
Why it was hard in games
State lived in managers and singletons; nothing owned anything, so everything depended on everything.
7 cross-feature calls across 4 layers
Today: four layers, and every feature smeared across all of them, wired to the others at every layer. Removing one leaves holes in four layers.
What a slice contains
features/battlepass/ battlepass.conjuremodule BattlePass { … } BattlePassView.csbinds the view model BattlePassSystems.csoptional per-frame logic art/prefabs, sprites
The feature's state, rules, UI view and frame logic sit together. Delete the folder and the feature is gone; copy it and the feature moves. A module groups the feature's tables, command and reactive query. The CLI generates an interface per module — IBattlePass for module BattlePass, as the docs show ICore for module Core — and the UI view binds the reactive query, its view model, through the generated context:
var track = context.Track(playerId: player.Id);passView.Bind(track.Current);track.SubscribeChanged(changes => passView.Apply(changes));
module BattlePass {table Tier(persistence: local, type_id: 7101, schema_version: 1) {Id: int @idXpRequired: int}table Progress(persistence: local, type_id: 7102, schema_version: 1) {PlayerId: int @idXp: intClaimedTier: int}type ClaimTierResult {tier: int}command ClaimTier(playerId: int, tier: int) -> ClaimTierResultkind: local{require @tier > 0 else InvalidTierupsert Progress| key { PlayerId = @playerId }| set { ClaimedTier = @tier }| returning { tier = ClaimedTier } into claimedreturn { tier = claimed.Tier }}reactive query Track(playerId: int) -> Progress[] {from Progress p| filter p.PlayerId == @playerId}}
Why it holds together without coupling
One owner per table
A module owns its tables. Other slices read through declared queries and joins, never through a manager reference.
Commands are the only writes
State changes go through the module's commands. There is no cross-feature method call to break when a slice changes.
UI views bind to view models
A UI view subscribes to the module's view model. No presenter knows about other features' events.
The same slice runs on the server
prototype validatedCommands are deterministic, so the module's logic is the server's logic too.
Carrying a feature to the next game
- 01
Copy the folder.
- 02
Compile: unresolved references come back as diagnostics, not runtime surprises.
- 03
Point its declarations at the new game's items and currencies; the compiler re-checks every join and regenerates what depends on them. Override only what the new game truly does differently.
What travels: the schema module, its generated code and the UI view logic. What you adapt: the references to the new game's model, then art, layout and balance.
What this is not
Not microservices
One process, one context on the device. Slices are compile-time ownership, not network boundaries.
Not runtime plugins
Nothing is loaded dynamically. Slices are compiled together; the compiler checks the whole.
Not free of design
A slice still needs a clean boundary. The compiler enforces ownership; you still choose it.
Start with one slice
Pick a feature you have rewritten twice. Declare it as a module, bind one UI view, and see what the compiler says about the rest.