iOS¶
Kanama's iOS backend is Supported on Godot 4.7 stable: it runs full Kanama
project scripts through the same generated Godot API wrappers as desktop/Android,
via a C GDExtension shim + a Kotlin/Native static .xcframework (no JVM on
device). Device-validated on iPhone 12 + iPhone 15 Pro.
Use the Godot 4.7 stable archive for the matching editor binary and iOS export templates.
The design is:
- Godot GDExtension entry point in a small C shim,
- Kotlin/Native static library linked into the same iOS
.xcframework, - no desktop JVM, GraalVM, or TeaVM in the iOS app, and
- physical-device validation first; simulator builds are optional compile/link checks and are not used as a performance signal.
Current Status¶
The iOS backend builds debug and release iOS .xcframework artifacts with
device arm64 and optional Apple Silicon simulator arm64 slices, and runs
full Kanama project scripts through generated Godot API wrappers over a C-shim
generic ptrcall.
The full ten-step device gate (scripts/ios_device_gate.sh: the
fresh-project install path plus the nine-demo matrix) is validated on two
physical models: iPhone 12 (iOS 26.5, 2026-06-25; 0 guardrail hits,
per-frame Kanama binding overhead about 0.63 ms/frame measured there) and
iPhone 15 Pro (iOS 26.5, 2026-07-10, on the full-breadth generated-wrapper
runtime). The claim names those two models — it is not a device-family
("recent iPhones") claim.
iOS is Supported on 4.7 stable, with caveats: the packaged .xcframework
addon is runtime-only (compiling a project's Kotlin scripts still needs the
Kanama checkout), there is no mobile hot reload, and one FPS Audio autoload
follow-up plus task-26 multiplayer UI polish are tracked as non-blocking — see
Version Support.
Toolchain¶
Use the current Kanama preview baseline:
| Tool | Version / Setting |
|---|---|
| Godot | 4.7 stable |
| Godot export templates | 4.7 stable iOS templates |
| Desktop runtime/build JDK | JDK 25+ |
| Xcode | 26.5 or newer enough to provide the installed iOS SDK |
| iOS runtime | Kotlin/Native static library inside an .xcframework |
| Device architecture | arm64 |
| Minimum iOS version | 14.0 by default |
Install the Godot iOS export templates for the same Godot version that opens or
exports the project. If xcode-select points at Command Line Tools, set
DEVELOPER_DIR for Gradle and Xcode commands so Kotlin/Native and Xcode use the
full Xcode app.
Configure signing through Xcode, Godot export presets, or standard Apple Developer account setup. Do not commit workstation-local paths, device UDIDs, team IDs, provisioning profile names, or private maintainer notes.
Runtime Shape¶
flowchart LR
EXPORT["Godot iOS export"] --> SHIM["C shim"]
SHIM --> KN["Kotlin/Native runtime"]
KN --> WRAP["Generated wrappers"]
WRAP --> OC["ObjectCalls"]
OC --> PTR["generic ptrcall<br/>dispatch (C shim)"]
PTR <--> GODOT["Godot Engine iOS"]
See Architecture: iOS for how this maps onto the desktop/Android runtime model.
Build The iOS Artifacts¶
From the Kanama checkout, build the device .xcframework artifacts:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
./gradlew assembleIosDeviceKanamaXcframework \
-PkanamaXcodeDeveloperDir=/Applications/Xcode.app/Contents/Developer
The task writes:
build/ios/xcframework-device/debug/kanama_ios.debug.xcframework
build/ios/xcframework-device/release/kanama_ios.release.xcframework
installIosAddon also defaults to device-only xcframeworks. Pass
-PkanamaIosXcframeworkMode=full only when intentionally building the optional
device plus simulator xcframeworks.
Packaged iOS Addon (prebuilt runtime)¶
Maintainers can package the prebuilt device runtime as a zip:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
./gradlew packageMobileAddonIos \
-PkanamaXcodeDeveloperDir=/Applications/Xcode.app/Contents/Developer
This writes build/distributions/kanama-mobile-addon-ios-v<version>.zip
containing both device arm64 xcframeworks (debug + release — the static
libraries are large, roughly 200 MB debug / 88 MB release before zip), a
.gdextension entries fragment to merge, and an install README.
Honest caveat: the packaged addon delivers the runtime only. Compiling a
project's Kotlin scripts for iOS still requires a Kanama source checkout
(installIosAddon runs the Kotlin/Native + KSP build). Use the zip to update
the prebuilt runtime or for script-less evaluation; the source-checkout flow
below remains the full workflow.
Validate a packaged zip without a device:
scripts/package_install_smoke.sh --ios-addon \
build/distributions/kanama-mobile-addon-ios-v<version>.zip
Install Into A Project¶
Install the Kanama iOS addon into a Godot project:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
./gradlew installIosAddon \
-PkanamaIosProjectDir=/absolute/path/to/godot_project \
-PkanamaXcodeDeveloperDir=/Applications/Xcode.app/Contents/Developer
If the project has Kotlin scripts in kotlin-src/, also pass the script source
directory so the iOS Kotlin/Native runtime is built with the project's generated
registrars:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
./gradlew installIosAddon \
-PkanamaIosProjectDir=/absolute/path/to/godot_project \
-PkanamaIosProjectScriptsDir=/absolute/path/to/godot_project/kotlin-src \
-PkanamaProjectScriptsDir=/absolute/path/to/godot_project/kotlin-src \
-PkanamaXcodeDeveloperDir=/Applications/Xcode.app/Contents/Developer
Passing both script directory properties keeps iOS and desktop/editor metadata
in sync during export. The export-time editor uses the desktop metadata to keep
scene-stored @ScriptProperty values, and the iOS runtime uses the iOS
registrars to load the scripts on device.
This installs the iOS descriptor entries:
[configuration]
entry_symbol = "kanama_entry"
compatibility_minimum = "4.7"
[libraries]
ios.debug.arm64 = "res://addons/kanama/bin/ios/kanama_ios.debug.xcframework"
ios.release.arm64 = "res://addons/kanama/bin/ios/kanama_ios.release.xcframework"
Coexistence With Desktop And Android¶
installIosAddon writes the iOS xcframeworks and preserves the normal desktop
Kanama library entries in addons/kanama/kanama.gdextension. If the project
already has Android plugin metadata, the task also preserves the Android AAR
entries instead of replacing them.
After install, one Kanama addon can contain desktop, Android, and iOS entries.
Do not hand-edit the .gdextension file to remove another platform unless the
project is intentionally dropping that platform.
For simulator experiments, add -PkanamaIosXcframeworkMode=full.
Godot Export Requirements¶
An iOS export needs the normal Godot iOS setup plus the installed Kanama iOS addon:
- Godot 4.7 stable editor or headless binary.
- Godot 4.7 stable iOS export templates installed.
- A Godot export preset named
iOS, or the equivalent preset name used in your command. architectures/arm64=truefor physical-device builds.application/export_project_only=trueif you want Godot to write an Xcode project that you build and install yourself.- A unique
application/bundle_identifier. - A valid Apple Development team, signing certificate, and provisioning setup for the target device.
addons/kanama/kanama.gdextensionregistered in.godot/extension_list.cfg.
For a fresh project, create the export preset in Godot's Export window, choose iOS, set the bundle identifier, select signing/team values, and save the preset before using the CLI examples below.
Signing And Provisioning¶
iOS device installs require an Apple Development certificate, a provisioning profile that includes the connected device, and a bundle identifier owned by the selected Apple team. Xcode's automatic signing can create or update development profiles when the Apple account has permission; otherwise create the identifier and profile in the Apple Developer portal, then select them in Xcode or the Godot export preset.
Keep signing values local. Public docs and committed scripts should use placeholders for device identifiers, team IDs, and profile names.
Common failures:
| Symptom | Likely Cause / Fix |
|---|---|
| Godot cannot export for iOS | Install the iOS export templates for the same Godot 4.7 stable editor. |
| Xcode reports no signing team | Set the Apple Development team in the export preset or Xcode project. |
| Xcode cannot create a provisioning profile | Sign in to Xcode with an Apple Developer account and allow provisioning updates, or create the profile in the Apple Developer portal. |
| Device is not a valid destination | Connect and trust the iPhone, enable Developer Mode, and use xcrun devicectl list devices to confirm the device identifier. |
| Kanama scripts load without scene property values | Re-run installIosAddon with both -PkanamaIosProjectScriptsDir and -PkanamaProjectScriptsDir. |
| Godot warns about a missing iOS GDExtension library | Re-run installIosAddon and confirm the ios.debug.arm64 and ios.release.arm64 entries exist. |
Export An iOS App¶
Export from the Godot editor or with the CLI. The CLI form mirrors Godot's
normal iOS export path. With application/export_project_only=true, Godot
writes an Xcode project into the directory of the export path instead of
producing an .ipa:
godot --headless \
--path /absolute/path/to/godot_project \
--export-debug iOS /absolute/path/to/export/KanamaGame.ipa
Use --export-release for a release preset after signing and provisioning are
configured for release. The generated Xcode project is the next step for device
install and launch.
Run On Device¶
Open the generated .xcodeproj in Xcode, select a connected iPhone, confirm the
signing team, and run the app.
For command-line installs, use the generated Xcode project with your own bundle identifier, device identifier, and team value:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcodebuild \
-project /absolute/path/to/export/KanamaGame.xcodeproj \
-scheme KanamaGame \
-configuration Debug \
-sdk iphoneos \
-destination "id=<your-device-udid>" \
-derivedDataPath /absolute/path/to/derived-data \
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM=<your-apple-team-id> \
build
Then install and launch the built app:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcrun devicectl device install app \
--device <your-device-udid> \
/absolute/path/to/derived-data/Build/Products/Debug-iphoneos/KanamaGame.app
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
xcrun devicectl device process launch \
--device <your-device-udid> \
--terminate-existing \
com.example.kanamagame
If Xcode is allowed to manage profiles for this app, add
-allowProvisioningUpdates to the xcodebuild command. For public docs and
committed scripts, keep device identifiers and Apple team IDs as placeholders.
Maintainers should keep real values in private handoff notes or their local
shell environment.
Optional Simulator Template Check¶
Only do this when deliberately using the simulator path. Before simulator work
on Apple Silicon, verify that the installed Godot iOS export template has an
arm64 simulator engine archive:
./gradlew verifyIosGodotTemplate \
-PkanamaXcodeDeveloperDir=/Applications/Xcode.app/Contents/Developer
Or run the script directly:
scripts/ios_template_preflight.sh \
--xcode-developer-dir /Applications/Xcode.app/Contents/Developer
The simulator path remains available for Xcode link checks and loader-debug experiments, but it is not a performance gate for Kanama iOS.
If the installed Godot iOS template is missing arm64 simulator support, build
a matching Godot simulator library and pass it explicitly to maintainer smoke
tooling:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
scons platform=ios target=template_debug arch=arm64 simulator=yes precision=single
scripts/ios_visual_smoke.sh \
--godot /Applications/Godot.app/Contents/MacOS/Godot \
--simulator \
--godot-simulator-lib /absolute/path/to/libgodot.ios.template_debug.arm64.simulator.a \
--kanama-script-probe
Apple Silicon iOS simulators build and run arm64 simulator binaries. That is
separate from real-device arm64, which uses the iphoneos SDK instead of the
iphonesimulator SDK. Use a real iPhone for any playability or performance
read.
Optional Maintainer Smoke¶
The visual smoke script proves the native loader, Xcode device build, app install, app launch, and a simple Godot render path. With the Kanama probe flags it can also prove Kotlin/Native frame callbacks, script resources, or full KSP-generated project script execution. These probes are maintainer validation tools; they are not the primary user export workflow.
Set KANAMA_IOS_DEVICE and, when needed, KANAMA_IOS_TEAM in your shell or
private maintainer notes before running the script. Do not commit the real
values.
scripts/ios_visual_smoke.sh \
--godot /Applications/Godot.app/Contents/MacOS/Godot \
--kanama-user-script-probe \
--allow-provisioning-updates
Use lighter probe flags such as --kanama-probe or --kanama-script-probe for
loader/render checks. None of the modes prove hot reload.
Current Boundaries¶
- Physical-device export and launch are the first validation target.
- Simulator startup is optional for compile/link debugging and should not be used to judge iOS frame rate or gameplay feel.
- Kanama sets the iOS
AVAudioSessioncategory toAmbientwhen the iOS shim starts. This makes game audio deterministic with the Ring/Silent switch: audio plays when the device is in Ring mode, is muted by Silent mode, and can mix with other app audio. Projects that intentionally need background/media playback semantics should set their own iOS audio session category in native platform code after startup. - Hot reload is out of scope for the iOS backend.
- The audited type set and KSP registration path cover the current demo corpus,
including the heavy
tps-demo-kanama. iOS is Supported on 4.7 stable; the mobile promotion record and remaining polish (task-26 multiplayer UI) are tracked in the internal task repo. - The runtime calls Godot through backend-neutral generated wrappers and
prefers cached typed
ptrcalls over Variant-heavy or allocation-heavy paths. - The current playable demo set matches the Android-enabled public demo set plus
Bunnymark; the heavy
tps-demo-kanamaalso runs on device (its mobile touch/multiplayer UI polish is tracked separately). FPS is playable but still has an intermittent Audio autoload follow-up.