# Scenes, levels and content (game.engine) How a game is organised into scenes (title, levels, loading, cinematics, credits), how one follows another, how files are loaded and freed, and the content files (archetypes, scenes, scripts) a game grows by. Readable offline: `gripforge docs scenes`, or the MCP tool `gripforge_docs` (topic `scenes`). ## What it is for `game.engine` is the first kit of every game. It does not rewrite the runtime: the loop, the bus, variables, entities, zones, triggers, objectives, spawn and save already run in the player. It names that base and adds three layers a game grows by without engine code: **archetypes** (entity templates), **scenes** (places and screens) and **scripts** (rules compiled to triggers). It is transparent without content: no archetype, scene, script or start scene means no entity, rule, variable, event or save section, and the game plays as it always did. It is installed first in every new game and is a normal dependency of the kits that claim its vocabulary; removing it needs `with_dependents` and `confirm`. | Content | Collection | Read by | | --- | --- | --- | | archetype (`gripforge.archetype/1`) | `archetypes` | the engine and the kits that claim its components | | scene (`gripforge.scene/1`) | `scenes` | the engine, the world kit of the scene, layer kits | | script (`gripforge.script/1`) | `scripts` | the engine, compiled to trigger rules | | quest, dialogue, NPC, item | `missions`, `dialogues`, `npcs`, `items` | their own kits | ## Scenes A scene names its world (`{ kit, config?, doc? }`), its entries, its instances (`{ id, archetype, at, yaw?, place?, set? }`), zones, per-scene `data`, `layers`, `audio` and `ui`. A game without any scene uses the implicit scene `main`: kits read their global collections. A scene has: | Field | Values | Meaning | | --- | --- | --- | | `kind` | `3d` (default), `2d`, `interface`, `loading`, `cinematic` | `interface` has no world: a title, a main menu, credits (nothing 3D is drawn); `loading` is a loading scene | | `role` | `menu`, `level`, `credits` | its part in the game | | `next` | a scene id | what follows (Play from the menu, the end of a level, the verb `next_scene`); default: the next level | | `background` | `{ image }`, `{ video }`, `{ color }` | the backdrop of an interface scene | | `transition` | `replace`, `add` | how `load_scene` and `next_scene` bring it in | | `loading` | `overlay`, `none`, `background`, a scene id | how it waits for its files | | `native` | an engine scene | on Unity, Unreal and Godot, the engine's own scene, loaded by the engine's scene adapter | ```json { "id": "title", "kind": "interface", "role": "menu", "next": "level_1", "background": { "video": "media/title.webm" } } { "id": "level_1", "role": "level", "world": { "kit": "world.racetrack", "doc": "oval" }, "next": "level_2" } ``` A scene's `world` hands its config to the world kit (for example `world.terrain`), over the kit's own config. Kits that build or dress a scene implement `sceneEnter` and `sceneExit`; their data lives in the scene's `data` (for example `minion_routes`). ### Tools `gripforge game-scenes ` (MCP `gripforge_game_scenes`) lists, plans, creates, orders and links scenes, edits the flow and the menu, and lists and checks the assets of each scene (`gripforge game-scenes help`). ## The scene flow (`sceneFlow` config) Set with `gripforge_gamekit_configure { id: "game.engine", config: { "sceneFlow": { … } } }`. | Key | Default | Meaning | | --- | --- | --- | | `menu` | `overlay` (`scene` in every new game) | `scene`: the title of `ui.menu` is a scene of its own, entered first with no world behind it (the project's `role: menu` scene, else one synthesised); Play leads to the start scene. `overlay`: the title is drawn over the start scene | | `menuScene` | | the menu scene when several interface scenes exist | | `loading` | `overlay` | how a scene waits for its files: `ui.menu`'s loading screen, `scene` (the loading scene), `none`, `background` (the scene left stays until the next is ready) | | `loadingScene` | first loading scene | scene shown between two scenes when `loading` is `scene` | | `loadTimeout` | 30 | seconds a scene waits for its files before it is entered anyway | | `minLoadingMs` | 0 | shortest time a loading screen stays | | `showAfterMs` | 250 | a loading screen appears only if the files are still not in after this delay (preloaded files: no flash) | | `unload` | `release` | `release`: leaving a scene frees its instances and the memory of the files only it used (never the disk cache, never a preload asked elsewhere); `keep` | | `transition` | `replace` | `replace` or `add` (stacked) when neither the scene nor the call says | | `onFail` | `retry` | a file of the next scene failed: `retry`, `enter` (without it) or `wait` (until `engine.scenes.retry()` or `cancelLoading()`) | | `retries` | 2 | how many times `retry` asks again | | `loadingScreen` | | the dressing of the loading screen: image, title, tips | A scene loads its files before it is entered (`asset.preload`); the asset list of each scene (`scene_assets`) is generated from its content, can be edited, and is one more source for `asset.preload`. Progress is the real progress of the preload, never a simulated bar. ### Several scenes at once A change may keep the scene left: `engine.scenes.enter(scene, entry, { unload: false })` or `load_scene { scene, unload: false }`. The scene left is hidden and paused by default and resumes at once when the game comes back. A scene can be stacked on others: `push_scene { scene, below: "run" | "pause" | "hide" }` (an inventory over a frozen level), `pop_scene`. Every loaded scene is visible or hidden, running or paused at any moment: `set_scene { scene, visible, paused }`. This is saved with the game. Nothing a loaded scene uses is freed. ## Scripts and verbs A script compiles, in pure TypeScript shared by the server and the web runtime, to ordinary trigger rules that the player runs. ```json { "id": "s_cellar_door", "on": "door_cellar", "rules": [ { "when": { "interact": "self" }, "if": [ { "has_item": "cellar_key" } ], "then": [ { "set_state": { "open": true } }, { "play_clip": "open" }, { "set_solid": false }, { "take_item": "cellar_key" } ], "else": [ { "notify": "content.door.locked" } ], "once": true } ] } ``` Sugars for `when`: `interact`, `enter` / `leave` (zones), `scene` (`entered` / `left`), `talked`, `quest`, `state`, and `gives` on an archetype or instance. Engine verbs: `set_state`, `play_clip`, `set_solid`, `show`, `hide`, `enable_script`, `disable_script`, `load_scene`, `next_scene`, `push_scene`, `pop_scene`, `set_scene`, `end_game`, `call`, `set_talk`, `patch_data`, `add_data`, `remove_data`, `set_config`, `set_setting`, `set_component`. Kit verbs keep working (`give_item`, `has_item`, `add_money`, `damage`…) with the shorthand `{ verb: arg }`. An archetype has `kind` (the spawn factory; `prop` is the engine's), `asset` (`{ slot }` or `{ item }`), `tags`, `components` (claimed by a kit, or owned by the engine: `state`, `solid`, `model`, `size`, `model.placement`, `entry`), `gives` and `clips`. ## Hooks, events, services | What | Name | Detail | | --- | --- | --- | | Hook | `engine.scene.change` | `{ from, to, entry, reason, loading }` (reason: play, load_scene, next, enter, restart, push, pop); mutable `to`, `entry`, `loading`; cancellable (`engine.scene.refused` follows). Send the player elsewhere or keep them here | | Hook | `engine.scene.release` | `{ scene, next, assets }`; mutable `assets` (the ones still freed); cancellable: everything stays in memory, for a scene the player returns to at once | | Hook | `engine.scene.failed` | `{ scene, failed, attempts, action }`; mutable `action` (`retry`, `enter`, `wait`) | | Hook | `engine.spawn` | `{ kind, archetype, at, tags }`; mutable `archetype`, `tags`; cancellable | | Events | `engine.scene.entered`, `leaving`, `loading`, `progress`, `loaded`, `screen`, `failed`, `cancelled`, `pushed`, `popped`, `activated`, `state`, `unloaded`, `refused` | the scene life cycle | | Events | `engine.flow.title`, `engine.flow.ended`, `engine.flow.restarted` | the game flow | | Services | `game.engine` | the contract | | | `engine.content` | instances, `registerComponent`, the report | | | `engine.scenes` | `current`, `enter`, `push`, `pop`, `retry`, `cancelLoading` | | | `engine.scripts` | the verb registry, rules, enable and disable | | | `engine.flow` | `start`, `end`, `restart` | | Moddable | `archetypes`, `scenes`, `scripts`, `hooks`, `scene_assets`, config `sceneFlow` | `hooks` is live: a rule may add or remove a tap while the game runs | ```json { "id": "locked_keep", "hook": "engine.scene.change", "match": { "to": "keep" }, "if": [{ "var": "story.key", "eq": false }], "cancel": true, "reason": "locked" } ``` ## Save The section `kits["game.engine"]` keeps the current scene and entry, visited scenes, per-instance state, played once-rules, disabled scripts, quest statuses, and the visible and paused state of each loaded scene. An old save without it loads into the start scene. ## Engines The scene flow, the scripts and the hooks run in the web runtime and the Rust kit host (hooks: web and Rust). Godot, Unity and Unreal read the compiled content and the flow; a scene with `native` is the engine's own scene, loaded by its scene adapter. ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | The title shows over the first level | `sceneFlow.menu` is `overlay`: set `scene` | | A loading screen flashes | lower `showAfterMs` is not needed: raise it, or preload the files so none shows | | Memory stays high after leaving a scene | `unload: keep`, a tap of `engine.scene.release` cancels, or the files are shared with another scene | | A scene change does nothing | a tap of `engine.scene.change` cancelled it (`engine.scene.refused` says by whom) | | A scene comes back frozen | it was kept by `unload: false` and is paused: `set_scene { scene, paused: false }` | ## Logic tick Besides scenes, `game.engine` sets the rate of the game logic: `gripforge_gamekit_configure { id: "game.engine", config: { "tick": { "hz": 30, "maxCatchUp": 4, "systems": { "npc.core.decide": 3 } } } }`. Logic runs on a fixed tick, the game creator's choice from 5 to 120 Hz (default 30; strategy 10-20, action 30, twitch shooter 60; changeable live, by the authority in multiplayer), separate from the render frame and the physics step, rendering draws the last published poses interpolated between two ticks, so the game plays at the same speed at 30, 60 or 144 frames per second. `systems` gives a heavy system a period (`N` ticks, or `{ every, offset, hz }`); never code a throttle. `tick` is not moddable: every player of a match uses the same tick.