Scenes and levels
Usage guide of game.engine scenes: scene kinds and roles, next scene and backgrounds, the sceneFlow config (title as a scene, loading, unload, failures), stacked and kept scenes, archetypes and scripts with their verbs, the hooks engine.scene.change, engine.scene.release and engine.scene.failed, events, save, engines.
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 |
{ "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 <gkp_id> <action> (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.
{ "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 |
{ "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.