# Using the Game Kits from Godot (`GfGameHost`) Play the logic of a GripForge game inside a Godot 4 project: one node, `GfGameHost`, runs the Game Kits and tells your scene what happened. Readable offline: `grip-forge docs godot`, or the MCP tool `gripforge_docs` (topic `godot`). ## What it is for A GDExtension gives Godot the node `GfGameHost`. It runs the same Rust logic of the Game Kits as the web player — movement, combat, inventory, missions, saves — from the same game file. The addon also holds `GfGameView`, which draws what the kits ask: the models of the game, their animation clips, the camera, the lights and the scenery, from the same data the web player draws from. `gripforge game-export --target godot` makes the whole Godot project from a game. You may change the rules from GDScript by tapping hooks or adding registry entries. No rule of a kit is rewritten in GDScript. ## Getting started For a Godot user: no Rust, no compiler, no GripForge repository. The addon is prebuilt for every platform; the CLI fetches it. 1. **Install the CLI** (Node.js 22.19 or later): `curl -fsSL https://gripforge.ai/cli/install.sh | sh` on Linux and macOS, `npm install -g https://gripforge.ai/cli/gripforge-cli-.tgz` on Windows (the current version is in `https://gripforge.ai/cli/latest.json`). Then `gripforge login`. An older CLI: `gripforge update` (the prebuilt addon needs the CLI 0.3.52 or later). 2. **Sign in, then export your game** as a Godot project. The export downloads your game from your GripForge workspace, so `gripforge login` must be done first (once per machine); without it the export stops and says so. A web export you already have needs no sign-in (`--from-web`, below). ```sh gripforge game-export gkp_... ./my-godot-game --target godot ``` The CLI downloads the web export of the game, fetches the addon `gripforge_kits` of its own version for every platform (about 150 MB for the three platforms the first time, kept in a cache), checks each file against the sha256 it carries, and writes the project. `--platforms linux-x86_64` (or `windows-x86_64`, `macos-universal`) fetches only what you need. Nothing to build, nothing to pass by hand. A game you already exported for the web converts the same way: `gripforge game-export ./my-godot-game --target godot --from-web ./my-web-export`. 3. **macOS only: let the addon load.** The libraries are not notarized yet, so macOS quarantines them when they come from a download. Once, in the project folder: `xattr -dr com.apple.quarantine addons/gripforge_kits` (or allow them in *System Settings → Privacy & Security* when Godot asks). 4. **Open the folder in Godot 4.2 or later** (4.7 recommended; the standard build is enough): *Import* → `project.godot`. Restart the editor once if the class `GfGameHost` is not known at the first opening. 5. **Run it**: F5 in the editor, or `godot --path ./my-godot-game`. The scene `main.tscn` holds a `GfGameHost` (the rules) and a `GfGameView` (what the kits draw). 6. **Change a rule** in `game_mods.gd` (yours: an update never rewrites it) by tapping a hook of a kit: ```gdscript func _ready() -> void: if not host.is_running(): await host.started # hits on enemies count double (a hook of combat.core) host.tap_hook("combat.damage", func(payload, _hook): if payload.get("targetTeam") == "enemy": return {"set": {"multiply": 2.0}, "reason": "double"} return null, {"id": "double_hits"}) ``` `host.hooks()` lists the hooks of the game, with the fields each lets you change. More: *Hooks*, below. Updating the game or the CLI: run the same export again on the same folder. ## Prerequisites - **Godot 4.2 or later**, the standard build (GDScript) or the .NET build. The extension is built against the 4.2 API and declares `compatibility_minimum = 4.2`; it was run with 4.7.1. Godot 3 and Godot 4.0 / 4.1 cannot load it. - **Node.js 22.19 or later**, for the CLI. Nothing else: no Rust, no C compiler. - A game of your GripForge workspace (`gkp_…`), or a web export of it (`--from-web`). ### Platforms One release, `gfkits-v`, holds the addon for these platforms. *Run* says what was really executed before the package was made; a platform that was only inspected has the right container, CPU and exported symbols, and nothing more is claimed. | Platform (`--platforms`) | Systems | Libraries in the addon | Run | |---|---|---|---| | `linux-x86_64` | Linux x86-64, glibc 2.28 or later (Debian 10, Ubuntu 20.04, and later) | `libgfkits_godot.so` 38.0 MB, `libgfpresent.so` 8.2 MB | **Yes**: Godot 4.7.1, headless (the logic against the TypeScript reference, the display commands, an exported project) and on screen — the dungeon and the kart demonstrations exported with the fetched addon and played, their presentation modules as native code. | | `windows-x86_64` | Windows 10 and later, x86-64 | `gfkits_godot.dll` 37.1 MB, `gfpresent.dll` 13.3 MB | **Partly**, under Wine only: the three libraries load; the C interface passes its test, loads the native modules, registers them by kit and calls them. **Never opened in Godot, never run on Windows.** | | `macos-universal` | macOS, Intel and Apple silicon (universal files) | `libgfkits_godot.dylib` 73.6 MB, `libgfpresent.dylib` 42.6 MB | **No**: inspected only — both CPU slices, the exported symbols, the system library they need. Never loaded on a Mac. Not notarized. | `libgfkits_godot` is the extension (the rules of the kits); `libgfpresent` holds what draws the kits — their presentation modules as native code — and is loaded by the extension. The download is 33 MB for Linux, 33 MB for Windows, 74 MB for macOS (compressed), plus 0.1 MB shared. **Not supported yet:** Android, iOS, Linux on ARM, Windows on ARM. The web export of Godot: see *Web export*. ## Install by hand The export does this for you. To put the addon in a project of your own, copy the folder `addons/gripforge_kits` of an exported project, or unpack `gfkits-godot-addon-.tar.gz` of the release at the root of your project, and your game file: ``` addons/gripforge_kits/gfkits.gdextension addons/gripforge_kits/gfkits-native.json # version, ABI, the kits whose drawing is native code addons/gripforge_kits/view/*.gd # GfGameView addons/gripforge_kits/bin// # one folder per platform: the extension, the native modules game/my.game.json ``` A **game file** is one JSON object `{ "project": …, "lock": …, "seed"?, "physics"? }` — the project (`gripforge.gameproject/1`) and its lockfile (`gripforge.gamekits.lock/1`). Restart the editor once after copying the addon. The first headless `--import` of a project that has just received a GDExtension may abort at exit after doing its work; the next runs are clean. ## First example A scene with a `GfGameHost` child. The calls are those of the demonstration project of the addon (`main.gd`) and of its headless test `tests/inherit_test.gd`: ```gdscript extends Node3D @onready var host: GfGameHost = $GfGameHost func _ready(): host.load_game("res://game/demo.game.json") # content added from Godot to a registry of the kits host.register_entry("items", {"id": "moonstone", "name": "Moonstone", "stackable": false}) host.entity_added.connect(_on_entity_added) host.game_event.connect(func(topic, payload, event): print(topic, " ", payload)) if not host.start(): push_error(host.last_error()) return host.watch("combat.*") # a rule of combat.core changed from GDScript: hits on enemies count double host.tap_hook("combat.damage", func(payload, hook): if payload.get("targetTeam") == "enemy": return {"set": {"multiply": 2.0}, "reason": "double"} return null, {"id": "double_hits"}) func _on_entity_added(id: String, entity: Dictionary): var node := MeshInstance3D.new() # your scene for entity["kind"] add_child(node) host.bind_node(id, node) # the host places it after every step func _unhandled_input(event): if event is InputEventKey and event.physical_keycode == KEY_W: host.key("KeyW", event.pressed) ``` With `autostep` on (the default) the host steps in `_physics_process`; nothing else is needed for the game to run. ## Exporting a game ```sh gripforge game-export gkp_... ./my-godot-game --target godot godot --path ./my-godot-game # or open the folder in the editor ``` The Godot project is made from the **web export** of the game — the same game file, the same assets. The CLI downloads that web export under `/.gripforge/web` first; with `--from-web ` it converts one you already have and asks nothing of the game server: ```sh gripforge game-export ./my-godot-game --target godot --from-web ./my-game ``` **The addon is fetched for you.** The native libraries are published as a release `gfkits-v`: one archive per platform and one of what every platform shares. Each CLI carries the catalogue of one release — its version, and the sha256 of every archive and of every file in it. The export downloads the archives it needs from `https://gripforge.ai/cli/gfkits-native//`, checks each against the catalogue **it carries** (never against a file downloaded from the same place), unpacks it, and keeps the files in `~/.grip-forge/cache/gfkits-native//`. An archive that differs is refused and nothing of it is kept; a cached file is checked again before each use, so the second export needs no network. **Offline, or with an addon of your own:** `--addon ` takes an addon folder as it is and downloads nothing — one copied from another machine's cache, or one you built from the GripForge sources (the build script of the native libraries, `build.sh --out dist`, then `dist/godot/addons/gripforge_kits`). | Option | Effect | |---|---| | `--target godot` | Make a Godot project (`web`, the default, is the web export). | | `--platforms a,b` | Only these platforms of the addon (names of the table above). Default: all of them. | | `--addon ` | Use that addon folder (`gfkits.gdextension`, `view/*.gd`, `bin//…`) and download nothing. Also read from `GRIPFORGE_GODOT_ADDON`. | | `--with-web` | Also copy the `.wasm` set of the web export under `gripforge/web/runtime-rust/` (see *Web export*: packaging only). Without it the project holds no `.wasm`. | | `--from-web ` | Convert that web export instead of downloading one. | | `--renderer` | `forward_plus` (default), `mobile` or `gl_compatibility`, written in `project.godot`. | | `--check`, `--force`, `--json` | As for the web: preview, replace locally edited managed files, print the report. | What the folder holds: | Path | What | |---|---| | `gripforge/game.json` | The game: `project`, `lock`, the slots bound to a file (`assets`), and the **game schema** when the web export holds the logic modules of the kits (the entity rows then carry the columns of the game: `column(id, name)`). | | `gripforge/present.json` | What `GfGameView` reads: the presentation modules, the model and image of each slot, what the pack says of each model's clips, what the web interface of each kit reads. | | `gripforge/modules/` | The data files the presentation modules share (`shared-….json`). No module: they are native code of the addon. | | `gripforge/assets/` | The models, images and sounds of the game. | | `addons/gripforge_kits/` | The addon: the extension, its libraries, the view layer. | | `main.tscn`, `game_mods.gd`, `project.godot`, `README.md` | Yours. Written once; an update never rewrites them (`--force` does). `game_mods.gd` is where you tap hooks. | | `.gripforge/godot.lock.json` | The hash of every managed file, for the next update. | Updating is running the same command again. Files under `gripforge/` and `addons/gripforge_kits/` are replaced; if you edited one, nothing is written and the report lists it (`--force` replaces it). The export writes `.godot/extension_list.cfg`, so the project runs before it was ever opened in the editor (`godot --headless --path .` on a build machine). **No `.wasm` in a native project.** What draws a kit — its presentation module — is native code of the addon (`bin//libgfpresent`), compiled from the same source as the `.wasm` the web runs. The addon says which kits it has (`gfkits-native.json`, `presentation.native`: the 54 modules of the catalogue); the export names them by kit in `gripforge/present.json` (`{ "kit", "version", "native": true }`) and copies no `.wasm`. The report says how many (`nativeModules`). A kit the addon has no native module of — a third-party kit, an older addon — is named in a warning (`undrawnModules`, `"native": false`): its module is not copied either, and nothing of it is drawn by a module in the native project. The export refuses to write a `.wasm` outside the web set of `--with-web`. **Exporting from Godot** (the editor's own *Export*, to make a `.exe`, an `.app` or an `.apk`): the library of the target platform must be in the project — export with that platform (the default has them all). In Godot's export preset, add `*.json, *.glb` to the non-resource files to export; Godot copies the extension and the library of the native modules beside the executable (the `[dependencies]` of `gfkits.gdextension`). macOS: the library is not notarized; Godot's export signs the app that holds it. | Environment variable | Effect | |---|---| | `GRIPFORGE_GODOT_ADDON` | An addon folder, as `--addon`. | | `GRIPFORGE_GODOT_ADDON_URL` | Another place to fetch the release from: an `https://` address or a local folder holding `/` (a mirror, a downloaded release). Archives are checked against the catalogue of the CLI all the same. | | `GRIPFORGE_GODOT_ADDON_CACHE` | Another cache folder. | ### Errors of the addon fetch The message ends with the code in square brackets. | Code | Meaning | What to do | |---|---|---| | `godot_addon_version_not_published` | The release `gfkits-v` this CLI expects is not at the origin. | `gripforge update`, or `--addon `. | | `godot_addon_unreachable` | The origin did not answer (offline, DNS, server error). | Run again, or `--addon `. | | `godot_addon_integrity` | An archive, or a file in it, differs from the catalogue of the CLI. Nothing was kept. | `gripforge update`; report it if it persists. | | `godot_addon_platform_unknown` | `--platforms` names a platform that does not exist. | Use a name of the table. | | `godot_addon_platform_not_available` | `--platforms` names a platform of the table the addon of this CLI is not built for yet. | Export for the other platforms. | | `godot_addon_not_catalogued` | This CLI carries no addon catalogue. | `gripforge update`, or `--addon `. | ## Web export **A Godot project made by this export does not run as a Godot web build today.** In a browser the native library cannot be loaded: the class `GfGameHost` does not exist there, so the main scene does not start. What exists: - **`--with-web` (packaging only).** The export also copies, under `gripforge/web/runtime-rust/`, the `.wasm` set the web player loads for this game — the logic modules of the game's kits (`kit-modules/`, with `kit-modules.json`), their presentation modules (`modules/`, `present-modules.json`), and their loader (`module-calls.mjs`, `kithost-wasm.mjs`, `kithost-bridge.mjs`, the game schema). They are the files of the web export, unchanged; the web player itself is left out. `gripforge/present.json` says where they are (`web.runtime`) and `web.status: "packaging-only"`. Without the option, a project holds no web set. Nothing loads these files from Godot yet. - **What is missing: the bridge.** A `GfGameHost` for the web that keeps the same GDScript API and hands every call to the page, where the loader of the web player runs the same `.wasm` modules. Not written. The plan and what each Godot version allows: `grip-forge docs godot-host`, §13. For a game in a browser today, use the web export (`gripforge game-export `): it is the same game, drawn by the web player. ## Drawing the game: `GfGameView` `GfGameView` (a `Node3D`, `addons/gripforge_kits/view/gf_game_view.gd`) needs a `GfGameHost` beside it. Every drawn frame it steps the logic, asks the host for the frame of the presentation and applies it: - **The entities.** One `Node3D` per entity of the game at its transform, **interpolated** between the two last ticks. The model bound to its slot is read from the `.glb` and sized by the rules of the web player (a character stands 1.8 m, a door frames its opening, `model.placement`…). Entities appear and disappear with the game. - **The clips.** The clip of the state the kit asks for (`model.anim`): the one the pack maps to the state, else one of the same name, else one alike, else idle. One-shot states (`attack`, `hit`, `death`…) play once at the speed that fits their duration; a change cross-fades in 0.12 s; clips advance with the time of the game. - **The camera** the kits describe in the `camera.*` state vars: top-down (perspective or orthographic), chase, first person; and what a module imposes (a pose, a shake). - **The scene.** The default look of the web player (daylight, or the dark look of top-down rooms), then what the kits set over it: the sun, the fill, the exposure, the background, the fog and the bloom of `world.lighting`, its lamps. - **What the kits draw themselves.** The presentation modules of the kits — the same `.wasm` the web player runs — are run by the host; `GfGameView` applies their commands: meshes, textures, standard materials, nodes, instances, models of slots, lights, ground rings. | Property | Role | |---|---| | `host_path` | The `GfGameHost`; empty: the first one among the siblings and the children. | | `present_path` | The presentation file (`res://gripforge/present.json`). | | `drive` | Step the logic from here once per drawn frame (default on; it turns the host's `autostep` off). | | `provisional_hud` | Draw the provisional HUD (below). | | `forward_input` | Send the keyboard and the mouse buttons to the game as web key codes. | | Member | Role | |---|---| | `report() -> Dictionary` | What is drawn and what is not: `{ frames, entities, looks, nodes, meshes, materials, lights, modules: [{ kit, calls, slept, cmds, msPerFrame }], msPerFrame, commands: { name: times }, refused: { what: times }, undrawn, unbound }`. | | `hud_data() -> Dictionary` | Per kit that has a web interface: `{ layer, vars, views, shared }` — the data `ui.webui` gives that kit's interface. | | `views`, `camera`, `snapshot` | The view of each entity (`{ root, look, entity, playing }`), the `Camera3D`, the snapshot of the frame. | | `started(info)`, `frame_presented(snapshot)` | Signals. | **What the modules shade and animate.** A material shaded by a module's WGSL (`MaterialWgsl`: skies, clouds, roads, water, effects) is drawn by a Godot shader written from that WGSL by the shader tool of GripForge (`vfx-naga godot`) — the functions of the module, unchanged, in a Godot frame. The view also draws particles (`Emitter`), trails, ribbons, decals, billboards, post passes (`PostPass`, on the displayed frame), a sky image (`Environment` with `sky`), recoloured materials of a model (`EntityMaterial`), and applies the clips a module poses an entity with, bone overrides and the posed skeleton it asks for. **What is not drawn is not invented.** A command `GfGameView` does not apply is answered `Refused` to its module and counted in `report().refused` (`report().refusedByModule` says which module asked). Not drawn, or drawn in part, today: | Not drawn, or in part | What you see instead | |---|---| | A WGSL material Godot's shading language cannot hold (the translation or Godot refuses it) | Its standard base material; the module is told why (`Shader { ok: false, message }`). | | In a WGSL material: `tone_mapped: false`, the normal map of the base of a `Surface`, `depth_bias`, the scene depth (`gf_scene_depth` answers far) | Tone mapped colours; the geometric normal; the order of the material; no soft intersection. | | A post pass before the tone curve, or one that reads the depth or the camera (`gf_depth`, `gf_world_from_clip`) | The pass runs on the displayed frame; depth answers far, the matrix is the identity. | | Surface layers over every material (`SurfaceLayer`: snow, rain) | Nothing. | | Two-bone IK (`IkTwoBone`) | The pose without it. | | The reflections of an environment image that is not the sky (`Environment` with `sky: false`), radiance probes | The fill of the scene, as before. | | Stand-ins of entities without a model that no module draws | Nothing; `report().unbound` counts the slots without a file. | | Render targets, UI images and UI shaders, picking (`PickParts`), reading a texture back (`ReadTexture`), per-node clips and meshes (`NodeClips`, `NodeMeshOwn`, `NodeTexture`), clear coat, audio (`AudioPlay`) | Nothing. | | The ray casts that keep the chase camera out of walls | The camera may enter a wall. | | `EntityLook.tint` | The untinted model. | Lights are converted from the units of the web; the picture is close to the web player's, not identical (shadow softness, tone curve of the background). **Cost.** The presentation modules of the kits run as native code: the addon ships them in one library (`bin//libgfpresent.*`, built from the sources of the web's `.wasm` and checked to answer the same bytes), which `GfGameView` loads by itself and registers by kit. An exported project holds no `.wasm`; the interpreter of the library (wasmi) is only for a module you load yourself with `present_module(bytes)`. Measured on the second machine at rest (Godot 4.7.1 Forward+, RTX A500, 1280×720): the dungeon demonstration (13 modules, 165 entities) 39–50 frames per second with 0.6–0.7 ms of modules per frame; the kart demonstration (5 modules) 56–59 frames per second with 3.2–3.7 ms (measured the same day with the interpreter: 3.6–7.3 ms and 25–47 ms). See `gripforge docs godot-host`, §6 and §11. ### Which kits run in Godot Every kit whose logic is in the addon's library runs in Godot, with the same rules as the web: there is no Godot version of a kit to wait for, and no rule to rewrite in GDScript. The catalogue says it by itself — `gripforge kit api ` (section *Targets*), the MCP tool `gripforge_gamekit_api`, the search of the catalogue (`status`, `gaps`) — as `godot: native (libgfkits)`; the same holds for `unity` and for the desktop player (`rust`). When something is still missing on these targets, it is named there, never left out: - `view to provide by a ui.webui fragment`: the logic runs, but that interface of the kit is drawn on the web page only; on Godot, Unity and the desktop a kit's interface is its `ui.webui` fragment, and this one has none yet. - a fragment *not yet in the published release*: the fragment exists, the next release of the addon brings it. - a part of the network core not available natively (WebRTC, the shared physics of `net.physics`): see *Known limits*. - a kit that refuses a native project says why. So before writing a mechanism of your own in GDScript, read the kit's *Targets*: if it says `native (libgfkits)`, use the kit (its config, hooks and mods) instead. The export lists any presentation module the addon does not have. ### HUD and menus The interface of the kits is `ui.webui` on every engine: web content — the fragments of the kits, the same page as the web. In Godot the `ui.webui` module of the addon's library runs that page in its browser engine and `view/gf_webui.gd` (started by `GfGameView` when the game has fragments) shows it over the game and gives it every input event before the game: what the interface takes never reaches `_unhandled_input`. The export ships the fragment code (`gripforge/modules/*.ui.mjs`), the addon the host page (`addons/gripforge_kits/webui/`). - The browser engine is embedded (never the browser of the machine) and installed once into a cache shared by every game of the user: the first time the game starts (it plays meanwhile, a band shows the download, the HUD appears when it is ready; a cut download is resumed), or when the game is installed — `godot --headless --path -- --prepare` installs it with no window and quits (0 = ready), `gripforge webui-engine prepare` without a game. `game-export --target godot --bundle-web-engine` puts it in the project (`webui-engine/`, ignored by the editor): nothing is downloaded, ever; `--webui-engine-when install` never downloads while the game plays; `--webui-engine-url ` changes the address (default: the site of GripForge). Where it is looked for: the project (or beside an exported game), `GRIPFORGE_WEBUI_ENGINE_DIR` (or `--webui-engine-dir=` after `--`) or the cache of the user, then a download from `GRIPFORGE_WEBUI_ENGINE_URL` (or `--webui-engine-url=`). The engine is published for Linux x86-64, Windows x86-64 and macOS (Apple silicon and Intel), about 130 to 155 MB to download; run on screen on Linux only. The cache of the user is `~/.cache/gripforge/webui-engine/` on Linux (`$XDG_CACHE_HOME/gripforge/…` when set), `%LOCALAPPDATA%\GripForge\webui-engine\` on Windows and `~/Library/Caches/GripForge/webui-engine/` on macOS; the engine of a version lives in its own folder there, `-/` (for example `154.0.34-1-macos-aarch64/`). An archive of the engine has no folder at its top: to install one by hand, unpack it INTO that folder (`mkdir -p /- && tar xzf -C /-`), or let `gripforge webui-engine prepare` do it. - **No interface is never silent**: without the module, the host page or the engine, the game runs and the named reason (`webui-module-missing`, `webui-host-page-missing`, `webui-engine-no-archive`…) is printed and shown in a band at the top of the window. - In Godot the picture of the interface is copied through memory (`cef-copy`), and the log says so: `[ui.webui] FALLBACK cef-copy: host-no-shared-import`. A HUD at rest makes no picture. The shared GPU texture of the desktop player is not available in Godot yet. - A click on a button of a fragment becomes the command of its kit, as on the web; the menu of `ui.menu` (title screen, pause, Options) is a fragment too. Run on screen on Linux only. Where that interface did not start, or for a game without fragments, the provisional rendering is drawn, marked as such on screen: - `GfGameView.hud_data()` answers, per kit, what its web interface reads (state vars, service views, shared keys); - the UI trees the modules send (`UiTree`: boxes and texts already resolved with the theme) are drawn as plain rectangles and texts; - the rest is listed as text under a red `PROVISIONAL HUD` banner. It is not the look of the game's interface. Set `provisional_hud = false` to draw your own from `hud_data()`. ## API reference `GfGameHost` is a `Node`. ### Properties | Property | Type | Role | |---|---|---| | `game_path` | `String` | A game file loaded and started in `_ready`. Empty: call `load_game` and `start` yourself. | | `autostep` | `bool` | Step in `_physics_process`. Default on. | ### Loading and starting | Method | Role | |---|---| | `load_game(path: String) -> bool` | Reads a game file. | | `set_game(game: Dictionary)` | The game as a Dictionary `{ project, lock }`. | | `set_option(key: String, value: Variant)` | Before `start`: `seed`, `fixedDt`, `physics` (`"rapier"` default, `"none"`), `schema`, `saves`, `locale`, `now`, `tzOffset`. | | `register_entry(collection: String, entry: Dictionary)` | Before `start`: adds an entry to a registry of the game (`items`, `statuses`, `archetypes`…); `extends` where the registry inherits. | | `start() -> bool` | Mounts the kits of the lock and starts the game. | | `stop()` | Stops the game. | | `is_running() -> bool` | | ### Time | Method | Role | |---|---| | `step(delta: float) -> int` | Advances the logic by `delta` seconds (fixed ticks inside), moves the bound nodes, emits the signals. Answers the ticks played. | | `step_ticks(ticks: int) -> bool` | Plays exactly `ticks` fixed ticks. | | `tick() -> int` | Fixed ticks played. | | `elapsed() -> float` | Seconds of game time. | ### Inputs | Method | Role | |---|---| | `key(code: String, down: bool)` | A key or mouse button by its web code (`KeyW`, `Space`, `Mouse0`). The bindings are the game's. | | `axis(action: String, value: float)` | An action of the game driven directly (an on-screen stick, Godot's own input map). | | `button(action: String, down: bool)` | An action pressed or released. | | `gamepad(buttons: PackedFloat64Array, axes: PackedFloat64Array)` | The pad, in the standard order. | | `input_specs() -> Variant` | The actions the kits declared (name, kind, bindings). | ### Entities | Method | Role | |---|---| | `bind_node(id: String, node: Node3D)` | The node follows the entity: its position and `rotation.y` are written after each step. | | `unbind_node(id: String)` | | | `entity_rows() -> PackedFloat64Array` | Every entity: 5 numbers each (id number, x, y, z, yaw). | | `column(id: String, name: String) -> PackedFloat64Array` | A column of the game schema for one entity. | | `entity(id: String) -> Variant` | `{ id, kind, tags, components }`; `"player"` is the current player. | | `snapshot() -> Variant` | The whole game (tools, tests). | ### State, config, registries | Method | Role | |---|---| | `state(path: String) -> Variant` | A state var (`player.health`…). | | `config(kit: String) -> Variant` | A kit's config as the game runs it, after the mods. | | `registry(collection: String) -> Variant` | A registry as the kits use it. | ### Services, operations, events | Method | Role | |---|---| | `call_service(service: String, method: String, args: Array) -> Variant` | A service of a kit: `host.call_service("inventory.items", "give", ["herb", 1])`. | | `apply(op: Dictionary) -> Variant` | One operation of the kit host (`emit`, `set`, `spawn`, `create`, `move`, `start_mission`, `present`, `command`, `hud`…). | | `apply_json(op: String) -> String` | The same, as JSON text. | | `watch(pattern: String)` | Events matching `*`, `combat.*`, `inventory.item.added` arrive by the signal `game_event`. | The services of a kit, its events and the operations are in its API reference (`grip-forge kit api `). ### Presentation Used by `GfGameView`; call them yourself only to draw the game another way. | Method | Role | |---|---| | `present_module(wasm: PackedByteArray) -> Variant` | Registers a presentation module (the bytes of its `.wasm`); answers `{ kit, version, needs }`. Before `present_start`. | | `present_start(options: Dictionary) -> Variant` | Starts the presentation: `{ engine, backend, caps, assets, modelSlots, textureSlots, generic, settings, shared, needs }`. | | `present_frame(delta: float, frame: Dictionary) -> int` | One drawn frame: `frame` is `{ camera: { position, rotation, fovY, viewport, viewProj, mode }, stage }`. Answers the number of commands waiting, -1 on a failure. | | `present_snapshot() -> Variant` | The snapshot of that frame: `{ phase, tick, elapsed, renderTime, vars, entities: [{ id, kind, tags, transform, model, components }], views, events }`. Transforms are interpolated. | | `present_commands() -> Array` | The commands of the modules, in order: `[module, name, fields]`. | | `present_result(module: int, result: Dictionary) -> bool` | An answer to a module (`{ "Refused": { "index", "reason" } }`, `{ "Loaded": … }`, `{ "Bounds": … }`…). | | `present_command(command: Dictionary) -> Variant` | A write of the view layer on the kit host, allowed by `needs.commands`. | | `present_shader(spec: Dictionary) -> Dictionary` | The Godot shader of a module's WGSL: `{ mode, fragment, vertex?, params: count, textures, base }` (a material) or `{ post: wgsl, params: count, texture }` (a post pass). Answers `{ ok, code, message }`. | | `present_info() -> Variant` | `{ started, frames, msPerFrame, stage, modules, needs, undrawn, logs, shared }`. | ### Hooks | Method | Role | |---|---| | `hooks() -> Variant` | The hooks the kits declared: per hook its kit, its `mutable` fields, whether it is `cancellable`. | | `tap_hook(hook: String, callable: Callable, opts: Dictionary) -> String` | Intercepts a hook; answers the tap id. `opts`: `priority` (-1000…1000), `id`. | | `untap_hook(id: String) -> bool` | Removes a tap. | `callable(payload: Dictionary, hook: String)` is called each time a kit decides the hook. It answers `null` (no change) or `{ "set": { field: value }, "cancel": true, "reason": "…" }`. From `tests/inherit_test.gd`: ```gdscript host.tap_hook("inventory.give", func(payload, _hook): if payload.get("item") == "herb": return {"cancel": true, "reason": "allergic"} return null, {"id": "godot_no_herb"}) ``` ### Saves | Method | Role | |---|---| | `save() -> String` | The game as a save (`gripforge.save/1`); it loads in the web player and back. | | `load_save(save: String) -> bool` | Loads a save. | | `store() -> Variant` | The local store of the kits (settings, save slots), for you to persist. | ### Status | Method | Role | |---|---| | `last_status() -> String` | The named status of the last call (`ok`, `refused`, `not_found`…). | | `last_error() -> String` | Its message. | | `library_info() -> Variant` (static) | What the library holds: `{ abi, contracts, physics, kits }`. | ### Signals | Signal | When | |---|---| | `started(info: Dictionary)` | The game started: `{ kits, skipped, fixedDt, lockHash }`. | | `stepped(ticks: int)` | After a step. | | `game_event(topic: String, payload: Variant, event: Dictionary)` | An event matching a watched pattern. | | `entity_added(id: String, entity: Dictionary)` | An entity appeared: create its node and bind it. | | `entity_removed(id: String)` | An entity is gone. | | `failed(status: String, message: String)` | A call failed. | ## Errors A method that fails answers `false`, `null` or an empty value, emits `failed(status, message)`, and `last_status()` / `last_error()` say why. The statuses are those of the [C API](c-api.md#errors): `bad_json`, `bad_project`, `no_game`, `wrong_thread`, `reentrant`, `panicked`, `refused`, `not_found`, `bad_schema`. - A tap that sets a field the hook does not list as `mutable`, a value its schema refuses, or a cancel on a hook that is not `cancellable` is refused by the game, not by the method: watch the event `engine.hook.refused` (`{ hook, tap, field, reason: "not_mutable" | "invalid" | "not_cancellable" }`). - A hook callable must not call the host back: the call is answered `reentrant`. - `started.skipped` names the kits of the game that have no logic in the library: the game runs without them. ## Known limits - **Drawing is partial**: see the table of *Drawing the game*. Module-shaded materials and effects are drawn; surface layers, IK, render targets, picking and audio are not. - **No start screen**: the title menu is a fragment of the HUD page, but a kit's start screen (the race of the kart preset, which the web page shows while it holds the game) is not: the race starts once the title closes. The HUD of the car football and of the kart race (scoreboard, objective, prompts, end of the match, race panel, results, mini-map) is drawn by the fragments of their kits, as on the web. - **HUD**: drawn by `ui.webui` (see *HUD and menus*), run on screen on Linux; the menus drawn as UI trees of a module are not shown by that page yet. - **CLI 0.3.51 and older fetch no prebuilt addon**: they answer `godot_addon_not_catalogued` unless `--addon ` names an addon folder. Run `gripforge update`: from 0.3.52 the CLI fetches the addon 0.2.1 (C interface 1.3, with the `ui.webui` module that draws the HUD) by itself; from 0.3.55 the addon 0.2.2 (C interface 1.4: sound, gamepad, full screen and pointer; the buttons of the interface act in the game; the browser engine of the interface downloaded). - **Platforms**: see the table of *Platforms* for what was run and what was only inspected. Windows and macOS were never opened in Godot on a real machine. Godot's web export cannot load the extension. - **The macOS library is not notarized**: a project opened in the editor on macOS may need the quarantine removed from the addon folder (`xattr -dr com.apple.quarantine addons/gripforge_kits`). - **A kit without a native module is not drawn by a module**: a native project loads no `.wasm`, so the presentation module of a third-party kit, which the addon does not have, is left out (the export names it). - **The native modules are proven identical to their `.wasm` on Linux x86-64 only**: the Windows and macOS libraries are the same sources built the same way, never replayed against the `.wasm`. - **Linux**: glibc 2.28 or later (Debian 10, Ubuntu 20.04, and later). No musl build (Alpine). - **Network kits** (`net.*`): the library serves the network core with its own sockets — TCP+UDP to `host:port`, then the WebSocket bridge (`url`, or `ws://host:port/path`) when `fallback` is on; the waiting room of `net.lobby` is drawn by its fragment. A Godot game joins the same room server as web players. Not available natively, and said by name when asked: WebRTC (a signalling server, `wss://` links: they need a browser), and the shared deterministic physics of `net.physics` (`sync.physics: "shared"`). A game hosting the room itself (`net.transport` role `host`) listens on its transport port (TCP+UDP and WebSocket) for the other players. - From C#, the class is reachable through Godot's binding of extension classes (`ClassDB.Instantiate("GfGameHost")`); no typed C# wrapper is generated, and this path is untested. ## Versions The extension declares `compatibility_minimum = 4.2`. It wraps the C API at ABI 1.3 ([c-api.md](c-api.md#versions)). The addon has the version of its package `gfkits-native` (`addons/gripforge_kits/gfkits-native.json`); each CLI fetches exactly one version of it, named in the report of the export (`addon.version`). Game files are `gripforge.gameproject/1` + `gripforge.gamekits.lock/1`; saves are `gripforge.save/1`.