Tutorial: Create a Playable Game
v1.0.111 · docs-public.v0This tutorial extends the world from Create a World into a playable game: a player can enter it, possess and control an entity, score points, win, and read a HUD. It is the natural sequel — the previous tutorial proved the world renders; this one is about making it playable, which is a different and stronger claim.
"Playable" is not "renders". A scene that draws on screen is not a game until the runtime can demonstrate that a player spawned, took control, and changed state. This page describes the canonical flow to author that; the closing section states exactly what proof is required before anyone may claim the result is playable.
This tutorial is verified against the current generated action, component, semantic-patch, and world-build references. The proof obligation below explains which runtime receipts must exist before a playable claim is accepted.
Media placeholder: add a capture showing the authored player, input binding, HUD state update, and final Play diagnostics/proof receipt.
Prerequisites
Start from a created world with a camera, a light, and a floor — see Create a World. As before, every edit goes through a canonical action or a semantic operation; the workbench shows a projection of authoritative state.
What "playable" requires in v1
The v1 acceptance model distinguishes a graph that contains the right pieces from a runtime that proves control. The minimum runtime evidence is:
- Camera — a viewpoint the runtime drives for the player.
- Spawn — the player (or controlled entity) is placed into the running world.
- Possession — input is bound to a controlled entity so the player can actually move and act.
- State change — the runtime shows authoritative state advancing (a score increments, a phase transitions), not just a static frame.
Until those hold under a live session, the world is not playable. See Explanation: Playability Proofs and Reference: Runtime Playability.
Step 1 — Add a player and a spawn point
Create a player entity with project.entity.create project.entity.create (MCP: project_create_entity project_create_entity). Give it component.transform for placement and component.collider so it interacts with the floor and props. Tag it (for example player) so scripts and the runtime can find it.
There is no PlayerControllerComponent and no SpawnPoolComponent in v1 — control and spawning are behavior, authored as scripts, not as component fields. A "spawn point" is simply the transform you place the player at on start; you can hold it as a named entity or as a typed-state field.
Expected result: a player entity stands in the world. It is inert — nothing controls it yet.
Step 2 — Bind input and possess the player
Input is declared and bound through the three canonical input components — InputActionDeclarationComponent InputActionDeclarationComponent, InputBindingSetComponent InputBindingSetComponent, and InputContextStackComponent InputContextStackComponent — which the workbench surfaces as a single composite Input palette entry. These are consumed by the runtime input, movement, and action systems (runtime.movementSystem, runtime.actionSystem). Read their field contracts in the ECS component reference before setting them.
With input declared, author movement and possession as a script on the player. Create the script shell with project.script.create project.script.create, then apply behavior with project.script.patch.apply project.script.patch.apply (MCP: project_apply_script_semantic_patch project_apply_script_semantic_patch). Use the mcp_safe operations:
addActionHandler— run logic when a declared input action resolves (move, jump, interact).addTickHandler— advance per-frame movement.
The compiler lowers these to canonical Script IR; you never write node ids on this path. See How-To: Write GessaScript and the Script Semantic Patch reference.
Canonical reference: Script Semantic Patch operationsExpected result: input is bound and the player has a control script attached. Whether the player actually moves under control is a runtime property to be proven, not assumed (see the closing section).
Step 3 — Make the runtime camera follow the player
Point the camera at the player so the viewpoint tracks control. CameraComponent carries followEntityId, followOffset, and followSmoothing fields; set followEntityId to the player and tune the offset. Update the camera through project.entity.component.set project.entity.component.set, reading the exact fields from the generated reference.
Expected result: the camera is rigged to follow the controlled entity — the "runtime-driven camera" half of the playability requirement.
Step 4 — Author a score and win loop with typed state
A game needs authoritative, durable state — a score and a win phase — not a number that lives only on the client. Author it with the typed-state semantic operations, which read and write warehouse state through validated paths:
writeTypedStateValue/setBooleanState— initialize the score and awonflag.incrementNumericState— add to the score deterministically when the player collects a prop or completes an objective (drive this from the prop'saddTriggerHandlerfrom the previous tutorial).compareRemainingCountanddeclareWinCondition— transition the match phase when the remaining-collectible count reaches the declared threshold.
These operations carry durable, writeState, and requiresAuthority effect tags — the win condition is decided by the server, not the client. See the operation list and effect tags in the semantic-patch reference.
The contract behind this step: typed state is a server-authoritative warehouse contract. A score that only updates in the browser is not state — it is a projection. The win is real because the authority wrote it.
Step 5 — Show a HUD bound to typed state
Create a HUD panel with project.ui_panel.create project.ui_panel.create (MCP: project_create_ui_panel project_create_ui_panel), then bind a widget to the score with the wireHudStateBinding semantic operation, which reads a state field and pushes it through UI.setWidgetValue for projection. The HUD reflects authoritative state; it does not own it.
Expected result: a score readout that updates from server state, and a win message when the win condition fires.
Step 6 — Play
Open Play in the workbench to run the game in an ephemeral preview session against the live Project Graph. Play is a workbench surface, not an MCP tool. Move the player, collect a prop, watch the score increment, and trigger the win. To publish a sharable build instead of an in-editor preview, use the publish action publish.dev publish.dev and then play the deployment — see How-To: Publish and Play.
The proof obligation
You have now authored everything a playable game needs. Authoring it is not the same as proving it. Here is the obligation that stands between "I built it" and "it is playable".
A spatial world is playable only when a live runtime session proves player spawn, possession, control-driven movement under a runtime-driven camera, and authoritative state change — not when a graph merely contains a camera, a player entity, and a script. The runtime playability and admission row is currently unaccepted.
The World Build Contract makes the acceptance policy explicit for a playable_game_factory build. Playable spatial-world acceptance requires, at minimum:
- a collision proxy,
- a nav/query proxy,
- a semantic anchor, and
playability.acceptedreceipts.
Its policy states the rule directly: renderability alone is not playable proof. Accepted build proof additionally requires semantic-contract proof, Project Graph validation, visible unsupported requirements, passing QA receipts, and a timeline receipt. The playable_game_factory claim names runtime proof lanes including runtime_play and factory_acceptance; missing proof is recorded as not_run, never silently as pass.
To claim this game is playable you need runtime evidence — collision proxy, nav/query proxy, semantic anchor, and a playability.accepted receipt — captured under a live session and recorded against the readiness registries. In the proof model, a markdown description of the behavior is explicitly insufficient evidence; a proof references exact source files and a focused runtime check and must be re-run after relevant changes.
So: build the flow above, open Play, and observe it — but state the result honestly. "It renders and I authored control and a win loop" is a true claim. "It is proven to be playable" is a claim you may only make once the proofs above exist and the corresponding ledger row is accepted. See Explanation: Playability Proofs for why the gate is shaped this way.
Where to go next
- Ship a build: How-To: Publish and Play.
- What "playable" formally means: Reference: Runtime Playability.
- The evidence model in depth: Explanation: Playability Proofs.
Status
This tutorial describes the canonical flow for authoring a playable game in v1. Every action, component, semantic operation, and tool named here is verified against the generated Tier-2 reference and the World Build Contract. The final playable claim tracks the runtime proof obligations above, not this prose.