Project Structure
Air Jam games should not grow as one large React surface.
The clean default is a boundary-first structure where host logic, controller logic, pure game rules, and framework integration do not collapse into the same modules.
Framework Recommendation
Move toward a structure like this:
src/
host/
controller/
game/
domain/
engine/
systems/
adapters/
ui/
debug/
shared/The exact folder names can vary slightly, but the boundaries should stay clear.
This is a framework-level recommendation about ownership boundaries, not a requirement that every Air Jam game use the exact same filenames.
Boundary Rules
src/host/
Owns host-only composition and shell behavior.
Examples:
- host screens
- room and join presentation
- host-facing overlays
- host runtime composition
src/controller/
Owns controller-only composition and touch interactions.
Examples:
- lobby controls
- gameplay controls
- controller status surfaces
- controller-specific UI hooks
src/game/domain/
Owns pure gameplay rules, math, and types.
Examples:
- score rules
- win conditions
- team assignment helpers
- deterministic state transitions
This layer should stay testable without React or rendering.
src/game/engine/
Owns runtime orchestration.
Examples:
- ticking
- lifecycle transitions
- update ordering
- system composition
src/game/systems/
Owns focused gameplay systems when the game has enough scope to justify them.
Examples:
- spawning
- combat
- pickups
- abilities
- scoring
src/game/adapters/
Owns integration with frameworks and runtime services.
Examples:
- Air Jam SDK integration
- R3F integration
- physics integration
- audio integration
- network and runtime bridges
Keep adapters thin. They should connect systems, not become the real game model.
src/game/ui/
Owns reusable game-facing UI modules.
Examples:
- score displays
- status strips
- overlays
- icon wrappers
tests/
Owns behavior-focused validation for the boundaries above.
Examples:
tests/game/domain/for pure rule teststests/game/stores/for pure state-transition teststests/game/engine/for focused runtime helper teststests/game/adapters/for transport-facing mapping and boundary teststests/game/ui/for shared game-facing UI primitives that can be validated without full app shells
Starter Template Reference
The starter Pong template is the current best starter implementation of those boundaries.
It is a recommended reference, not a framework API.
Its useful starter modules look like:
src/host/index.tsxfor host surface compositionsrc/controller/index.tsxfor controller flow and input cadencesrc/game/stores/pong-store.tsfor networked store wiringsrc/game/stores/pong-store-state.tsfor pure state transitionssrc/game/engine/simulation.tsfor runtime orchestrationsrc/game/engine/runtime-state.tsfor hot mutable runtime valuessrc/game/adapters/controller-signals.tsfor host-to-controller transport-facing mappingsrc/game/ui/for reusable game-facing presentation primitives shared by host and controllersrc/game/prefabs/arena/for a folder-per-prefab contract with metadata, schema, preview, and runtime composition helpertests/game/for the starter testing pattern that mirrors those same boundaries
If a starter module already demonstrates the boundary you need, extend it instead of introducing a second competing pattern. If your game genuinely needs a different folder or module shape, preserve the boundary intent rather than copying Pong mechanically.
src/game/debug/
Owns debug helpers and overlays.
Keep debug features removable and out of gameplay hot paths.
Decision Rule
When adding code, ask:
- is this pure game logic
- is this runtime orchestration
- is this framework integration
- is this host-only or controller-only
Put the code where the answer is clearest.
Refactor Rule
If a new feature would force host, controller, domain, and rendering concerns into one file, fix the boundary first or split the work into:
- structural cleanup
- feature implementation