State and Rendering
Air Jam projects often combine React, Zustand, per-frame gameplay logic, and sometimes R3F or Three.
That stack works well only when state ownership is explicit.
Core State Rules
- keep authoritative gameplay state separate from local UI state
- avoid one mega store for unrelated concerns
- use narrow selectors when consuming Zustand state
- keep store writes intentional and coarse
- keep per-frame mutable values out of React state when React rendering is not needed
These are framework-level rules about ownership and performance. They do not require one exact store or engine file layout.
React Rules
- do not run simulation through React rerenders
- keep business logic out of presentational components
- use refs for hot mutable runtime values
- keep component boundaries small and purposeful
Starter Template Reference
The starter Pong template demonstrates one good starter implementation of the state/rendering split:
src/game/stores/pong-store.tsowns Air Jam store wiringsrc/game/stores/pong-store-state.tsowns pure state transitionssrc/game/engine/runtime-state.tsowns per-frame mutable valuessrc/game/engine/simulation.tsowns update ordering and orchestrationsrc/game/ui/owns reusable game-facing presentation that host and controller surfaces can share
That split is a recommended starter pattern, not a mandatory framework contract. If the game needs a different module layout, preserve the same ownership boundaries rather than copying the filenames literally.
Starter Testing Pattern
The starter template also mirrors those boundaries in tests:
tests/game/domain/for pure gameplay rulestests/game/stores/for pure state transitionstests/game/engine/for focused runtime helperstests/game/adapters/for transport-facing mappingtests/game/ui/for shared game-facing UI modules that do not require full host/controller shells
The default order is:
- test pure logic first
- test thin adapter boundaries second
- add heavier runtime or browser-facing coverage only where the integration boundary actually matters
R3F and Three Rules
- avoid rerendering scene components on every frame
- mutate refs in frame loops when visual updates do not require React state
- keep rendering integration separate from pure game rules
- use instancing when repeated objects justify it
Air Jam Lane Rules
Keep the framework lanes separate:
- input lane for high-frequency player control input
- replicated state lane for replayable shared truth
- signal lane for coarse UX and system events
Do not use replicated store actions as a substitute for per-frame input handling.
Common Pitfalls
- putting gameplay logic directly in render components
- using React state as the simulation engine
- selecting whole stores across many components
- mixing debug state into hot runtime paths
- solving every state problem with more component state