# Cinematics and cutscenes (game.cinematic) Real-time introductions and cutscenes made of authored camera shots, with titles, captions, letterbox, fades, staged characters and props, animated actors, timed audio and spoken lines. Readable offline: `gripforge docs game-cinematic`, or the MCP tool `gripforge_docs` (topic `game-cinematic`). ## What it is for A cinematic is data: a sequence of shots that the player of your game plays over the live world. The kit pauses the simulation while the sequence advances on presentation time, releases the input, hides the HUD, then restores the gameplay camera under a short fade and resumes. Escape, the visible Skip button and the gamepad B button skip; the reduced-motion preference gives static framings. A sequence that is empty or invalid never locks the controls. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "game.cinematic" } gripforge_gamekit_configure { project_id: "gkp_…", id: "game.cinematic", config: { "once": false, "letterbox": 0.1 } } gripforge_game_project { project_id: "gkp_…", action: "data", collection: "cinematics", documents: [{ "id": "intro", "subtitle": "PROLOGUE", "shots": [ { "id": "reveal", "duration": 6, "space": "ground", "from": [-20, 18, -30], "to": [-12, 12, -22], "target": [0, 2, 0], "fov": 48, "endFov": 43, "title": "Your game", "caption": "Your story begins here." } ] }] } ``` Without an authored `intro`, the player plays a generated three-shot introduction after Play (or after the world loads when `ui.menu` has no title screen); it waits for model loading and an attached presentation. Authoring a sequence with the id `intro` replaces it. The kit requires `game.engine`. The full reference is `gripforge kit api game.cinematic`. ## Settings and defaults | Key | Default | Meaning | | --- | --- | --- | | `intro` | `intro` | id of the sequence played automatically | | `autoplay` | `true` | play it after Play | | `once` | `true` | once per saved run; `play(id, true)` replays | | `skippable` | `true` | Escape, Skip button, gamepad B | | `letterbox` | 0.115 | bar height, as a fraction of the screen | | `fadeIn`, `fadeOut` | 0.7, 0.6 | seconds | | `handoff` | 0.35 | seconds of the fade back to the gameplay camera | | `hideHud` | `true` | hide the HUD while playing | | `hidePlayer` | `false` | hide the player character | | `groundClearance` | 1.2 | metres kept above the terrain by ground- and player-relative cameras | A mod can change all of them and add, patch or remove documents of the `cinematics` collection. ## The sequence Up to 16 shots, 60 seconds each, 600 seconds in all; shot ids are unique. Coordinates are in metres. | Field of a shot | Meaning | | --- | --- | | `id`, `duration` | | | `space` | `world` (absolute XYZ), `ground` (altitude above `world.ground`), `player` (offsets rotated by the player's yaw at the start), `actor` (with `of: ""`: offsets rotated by the actor's heading, for an over-the-shoulder shot) | | `from`, `to`, `target`, `targetTo` | camera start, end, look-at point, optional moving look-at | | `fov`, `endFov` | lens | | `easing` | `smooth` (default) or `linear` | | `title`, `caption` | text shown during the shot | | `actors` | what changes when the shot begins: `{ id, at?, facing?, anim? }` | World and actor coordinates are exact; only ground- and player-relative cameras are kept above the terrain. Keep shots inside the loaded region of the world: a camera does not stream distant places. An optional `audio` names an existing `audio.core` sound and stops only that voice when leaving. ### Cast A sequence may carry up to 32 `cast` members, present only while it plays: `{ id, slot, variant?, at, facing?, space?, anim? }` (a model of your game as bound, for example `slot: "content", variant: ""`), or `{ id, player: true, at }` (a stand-in drawn with the player's model while the real player is hidden). `kind: "prop"` with `size: [w, h, d]` fits a model in a box of that size (at most 2000 m per axis); for a character, `size` fits the height with feet at `at`. A cast member can have `parent: "earlier_cast_id"`: its poses then become local to that parent (an exhaust on a vehicle, a carried prop). Cast members are presentation entities without collision or gameplay, and are removed on every exit. A space exterior can sit in a separate staging area of the loaded scene and hard-cut to the real interior. ### Animated timeline The optional `timeline` runs on presentation time, in seconds from the start of the sequence: - `tracks`: `{ actor, interpolation: "linear" | "smooth" | "cubic", keys: [{ time, at?, rotation?, scale? }] }`, one track per actor, 2 to 128 keys, first time zero, strictly increasing. Rotation is degrees `[pitch, yaw, roll]` (shortest way per axis), scale from 0.001 to 100. The last key holds. Cubic positions can overshoot: check the clearance. - `cues`: `{ id, time, actor?, anim?, sound?, voice?, volume?, pitch?, stopAfter? }`, up to 64, distinct ids. A clip cue restarts the actor's clip; `sound` names a bank entry of `audio.core` and the kit stops only its own voice (at `stopAfter`, on skip, end or cancel). During the pause, use a bank entry on the `music` or `ui` buses (or on `voice` with `voiceDuringPause` enabled); `sfx` and `ambience` stay muted. Every crossed cue fires `cinematic.cue` once. - `shakes`: `[{ time, duration, amplitude: [x, y, z], frequency? }]`, deterministic decaying camera translation; suppressed by reduced motion. - `poses`, `expressions`, `reaches` (acting tracks, 2 to 128 keys each): `poses` rotate a named bone (`bone`, `rotation` degrees, `weight`, `additive` default `true`); `expressions` drive a named shape weight from 0 to 1; `reaches` bring a chain of three bones (upper arm, forearm, end effector) to a world target with an elbow `pole`. Bone and shape names belong to your model; a name alone creates no geometry. These are cosmetic: no forces, collisions or gameplay commands. A speech cue `{ id, time, actor, voice: "" }` calls the `dialogue.voice` service on that staged actor (install `dialogue.voice` and `audio.core`, set `voiceDuringPause: true`, put the recording in the sound bank); it cannot be combined with `sound`, `volume`, `pitch` or `stopAfter`. Skip, cancel, end, scene exit and load stop cast speech. Timeline sequences are supported by the native player and the Rust kit host; the web runtime in JavaScript rejects them instead of showing static actors. ```json { "id": "departure", "cast": [{ "id": "vehicle", "slot": "content", "variant": "vehicle", "kind": "prop", "at": [0, 20, 0], "size": [8, 4, 12] }], "shots": [{ "id": "wide", "duration": 4, "from": [-20, 28, -25], "to": [-15, 25, -22], "target": [0, 20, 0] }], "timeline": { "tracks": [{ "actor": "vehicle", "interpolation": "cubic", "keys": [ { "time": 0, "at": [-8, 20, 0], "rotation": [0, 0, -10] }, { "time": 2, "at": [0, 22, 6] , "rotation": [-5, 20, 18] }, { "time": 4, "at": [12, 24, 18], "rotation": [0, 30, 0] }] }], "cues": [{ "id": "engine", "time": 0, "sound": "departure_engine", "stopAfter": 4 }] } } ``` ## Playing from your game | How | What | | --- | --- | | Service `game.cinematic` | `attach(on?)`, `play(id?, replay?)`, `skip()`, `cancel()`, `active()`, `view()`, `camera()`, `sequences()`, `setReducedMotion(on)` | | Script verbs | `{ "play_cinematic": "arrival" }` or `{ "type": "play_cinematic", "id": "arrival", "replay": true }`; `{ "skip_cinematic": {} }` | | Variables | `cinematic.active`, `cinematic.id` | Completed ids are saved; loading a save never replays the introduction. Active sequences are cancelled on load or scene exit so the player cannot stay stuck in a cutscene. ## Events and hooks | What | Name | Detail | | --- | --- | --- | | Events | `cinematic.started { id }`, `cinematic.shot { id, shot, index }`, `cinematic.cue { id, cue, time, actor, entity, audio }` | | | Event | `cinematic.finished { id, reason }` | emitted exactly once, `reason` is `completed` or `skipped`. Put required story changes here, so skipping still gives the intended final state | | Event | `cinematic.cancelled { id }` | load, scene exit or teardown; the sequence is not marked completed | | Hook | `cinematic.start { id, replay }` | before a sequence starts; a tap may change `id` to another loaded sequence, or cancel (`play` returns `false`) | | Hook | `cinematic.skip { id, shot, elapsed }` | before a skip; a cancel keeps it playing | `cancel()` is the safety exit and cannot be intercepted. ```json { "id": "no_skip_finale", "hook": "cinematic.skip", "match": { "id": "finale" }, "cancel": true } ``` ## Engines The web and native players play it; the timeline, acting tracks and voiced cues run in the Rust kit host and are presented by the native player. Other engine presenters are not certified for them. ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | The intro never plays | `autoplay: false`, `once: true` and it already played in this save, or the sequence id differs from `intro` | | A story change is missing after Skip | it was attached to a shot or a cue: move it to `cinematic.finished` | | An actor does not move | the sequence has no `timeline` track for it, or it runs in the JavaScript web runtime | | A cue sound is silent | it is on an `sfx` or `ambience` bus (muted during the pause): use `music`, `ui` or `voice` | | The camera dips under the ground | `world` space ignores the clearance: use `ground` space, or raise the points | ## First-person awakening (1.7) Add `wakeUp` to a sequence to open curved eyelids over the live world, blink and gradually regain focus. Install the example below in `cinematics`, then call `play("wake_up")`. Set `letterbox: 0` and `fadeIn: 0`. The player model is hidden automatically; camera positions do not move the gameplay character or switch its controller. ```json { "id": "wake_up", "wakeUp": { "closedDuration": 0.4, "openDuration": 3.2, "focusDuration": 5, "blur": 0.018, "blinkAt": 1.5, "blinkDuration": 0.35 }, "shots": [ { "id": "eyes_open", "duration": 7, "space": "player", "from": [0, 1.65, 0], "to": [0, 1.65, 0], "target": [0, 1.9, 6], "targetTo": [0, 1.65, 6], "fov": 70 } ] } ``` All durations are seconds. Opening and focus start after `closedDuration`; `blur` is a fraction of screen height (0–0.04, zero disables blur). Supply both `blinkAt` and `blinkDuration`, or neither. Opening, focus and blink must finish within the sequence. Adjust shots to your scene. A menu pause holds the effect; replay restarts it; skip, cancellation and handoff remove it. Reduced motion uses a simple fade without blur or blinking. `reducedMotion()` reads the preference; `cinematic.motion { on }` reports changes. This effect is implemented in the shared Rust presenter and validated in Bevy WebGPU and WebGL2. Other engine presenters and the historical JavaScript exporter are not certified for it. The furnished infirmary is an independent example, not a dependency of the kit.