Godot
Usage guide of the Godot addon: getting started in five steps without Rust, export a game as a Godot project (gripforge game-export --target godot: the prebuilt addon is fetched for Windows, macOS and Linux, checked and cached; --platforms, --addon offline, --with-web), supported Godot versions and platforms, what GfGameView draws of the kits (models, clips, camera, lights, module commands) and what it does not, the provisional HUD and hud_data(), every method and signal of GfGameHost (load_game, start, step, inputs, hooks, saves, present_*), errors, limits.
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.
- Install the CLI (Node.js 22.19 or later):
curl -fsSL https://gripforge.ai/cli/install.sh | shon Linux and macOS,npm install -g https://gripforge.ai/cli/gripforge-cli-<version>.tgzon Windows (the current version is inhttps://gripforge.ai/cli/latest.json). Thengripforge login. An older CLI:gripforge update(the prebuilt addon needs the CLI 0.3.52 or later). Sign in, then export your game as a Godot project. The export downloads your game from your GripForge workspace, so
gripforge loginmust 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).gripforge game-export gkp_... ./my-godot-game --target godotThe CLI downloads the web export of the game, fetches the addon
gripforge_kitsof 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(orwindows-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.- 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). - 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 classGfGameHostis not known at the first opening. - Run it: F5 in the editor, or
godot --path ./my-godot-game. The scenemain.tscnholds aGfGameHost(the rules) and aGfGameView(what the kits draw). Change a rule in
game_mods.gd(yours: an update never rewrites it) by tapping a hook of a kit: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<version>, 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-<version>.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/<platform>/<libraries> # 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:
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 #
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 <folder>/.gripforge/web first; with --from-web <web export folder> it converts one you already have and asks nothing of the game server:
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<version>: 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/<version>/, 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/<version>/. 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 <dir> 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 <dir> | Use that addon folder (gfkits.gdextension, view/*.gd, bin/<platform>/…) 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 <dir> | 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/<platform>/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 <version>/<archive> (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<version> this CLI expects is not at the origin. | gripforge update, or --addon <dir>. |
godot_addon_unreachable | The origin did not answer (offline, DNS, server error). | Run again, or --addon <dir>. |
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 <dir>. |
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, undergripforge/web/runtime-rust/, the.wasmset the web player loads for this game — the logic modules of the game's kits (kit-modules/, withkit-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.jsonsays where they are (web.runtime) andweb.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
GfGameHostfor 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.wasmmodules. 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 <gkp_id> <folder>): 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
Node3Dper entity of the game at its transform, interpolated between the two last ticks. The model bound to its slot is read from the.glband 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
.wasmthe web player runs — are run by the host;GfGameViewapplies 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/<platform>/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 <kit> (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 <id> 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 itsui.webuifragment, 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 <project> -- --prepareinstalls it with no window and quits (0 = ready),gripforge webui-engine preparewithout a game.game-export --target godot --bundle-web-engineputs it in the project (webui-engine/, ignored by the editor): nothing is downloaded, ever;--webui-engine-when installnever downloads while the game plays;--webui-engine-url <base>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=<dir>after--) or the cache of the user, then a download fromGRIPFORGE_WEBUI_ENGINE_URL(or--webui-engine-url=<base>). 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,<version>-<platform>/(for example154.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 <cache>/<version>-<platform> && tar xzf <archive> -C <cache>/<version>-<platform>), or letgripforge webui-engine preparedo 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 HUDbanner. 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 <id>).
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:
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: 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 notcancellableis refused by the game, not by the method: watch the eventengine.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.skippednames 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_cataloguedunless--addon <dir>names an addon folder. Rungripforge update: from 0.3.52 the CLI fetches the addon 0.2.1 (C interface 1.3, with theui.webuimodule 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
.wasmon 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 tohost:port, then the WebSocket bridge (url, orws://host:port/path) whenfallbackis on; the waiting room ofnet.lobbyis 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 ofnet.physics(sync.physics: "shared"). A game hosting the room itself (net.transportrolehost) 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). 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.