This file is the short operational map for coding agents and maintainers.
Public user documentation lives in docs/; keep this file focused on how to
work in the repository without scanning the whole tree or breaking Kanama's
release shape.
- Kanama release line:
0.4.0(tagged 2026-07-24; a pre-1.0 preview baseline). - Godot baseline: Godot
4.7 stable(re-pin only on stable releases; bumps followdocs/contributing/godot-upgrade.md). - Desktop runtime/build JDK: JDK
25+. - Mobile is Supported (4.7 stable), promoted from Experimental 2026-07-14 once
the §7 mobile promotion bar went green. Android is device-validated across
four models (Pixel 7, Moto g 5G 2023, Galaxy S10+, Pixel 3 XL): debug
validated to Android 9, but release builds require Android 13+. iOS is
device-validated on iPhone 12 and iPhone 15 Pro. Carried caveats: packaged
mobile addons are runtime-only (compiling project scripts needs the Kanama
checkout), the Android release path depends on the JitPack PanamaPort fork,
and there is no mobile hot reload. See
docs/reference/version-support.mdfor the full claims and validation dates. - Release artifacts use two shapes: desktop kits for new projects and store
add-ons for existing projects. See
docs/exporting/desktop.md.
Always confirm this section against README.md,
docs/reference/version-support.md, and CHANGELOG.md before changing release
wording.
If you're working on a specific roadmap task, read the matching file in
/Users/lmuller/dev/kanama-tasks/ first. Each task is standalone and points to
the smallest doc set and exact files to edit. The index is
kanama-tasks/README.md. For ad-hoc work not covered by a task file, use
"Read First By Task" below.
Avoid broad context scans. Start with the smallest doc set that matches the
task, then use targeted rg searches.
- Project status, requirements, and user quick start:
README.md,docs/getting-started/index.md, anddocs/reference/version-support.md. - Contributing to runtime, wrappers, generator, Android, docs, or packaging:
CONTRIBUTING.mdanddocs/contributing/index.md. - Bootstrap, FFI, script lifetime, ClassDB registration, script resources, or
hot reload:
docs/contributing/architecture.md. - Generated wrappers, generator policy, ABI helpers, KDoc sync, or wrapper
coverage:
docs/contributing/wrapper-maintenance.md. - Android runtime/export support:
docs/exporting/android.mdanddocs/contributing/android-internals.md. - New Kanama game projects:
docs/getting-started/source-checkout.md,docs/game-dev/scripts.md,docs/game-dev/godot-api.md,docs/game-dev/properties-resources.md, anddocs/game-dev/signals.md. - Porting GDScript or working on gameplay demos:
docs/game-dev/porting-gdscript.md,docs/contributing/demo-porting-rules.md, and the companion demo repo guide if present.
src/main/kotlin: Kanama runtime, bootstrap-facing Kotlin code, wrappers, value types, and editor/runtime support.src/generated/godot: generated Panama bindings from Godot headers.annotations: public KSP annotation definitions for game scripts.processor: KSP processor that emits script registrars and metadata.project-scripts: Gradle support for compiling consumer project scripts.templates/starterandtemplates/starter_project: source-checkout onboarding templates.templates/release-kitandtemplates/store-addon: packaged artifact templates.example_project: local smoke-test Godot project.scripts: CI, smoke, audit, packaging, and generator helper scripts.docs: public MkDocs documentation.
| Repo | Audience | Contents |
|---|---|---|
kanama/ (this repo) |
public | Runtime, wrappers, generator, docs. |
kanama-demos/ |
public | 19 public demos: Bunnymark, Match3, 3D-Platformer, dodge, squash, character-controller, Racing, FPS, third-person, tps-demo-kanama, City-Builder, and others. |
kanama-demos-private/ |
private | Private demos such as deformable-snow-kanama and plowpatrol; not for tracked files. |
kanama-maintainer-notes/ |
private | DEV_LOG.md and TASKS.md with device IDs, signing teams, and exact local commands; never copy values from here into tracked files. |
| File | Generated by | Notes |
|---|---|---|
src/main/kotlin/binding/runtime/ObjectCalls.kt |
scripts/generate_api_wrapper.py |
Large generated call surface with many confined-arena blocks; edit the generator, not this file. |
Generated wrappers under src/main/kotlin/ |
scripts/generate_api_wrapper.py |
Public wrappers must be regeneratable unless explicitly marked as hand-authored policy classes. |
src/generated/godot/* |
scripts/refresh_godot_api.sh from Godot headers |
Generated Panama bindings. |
docs/internals/reference/ios-backend-handwritten.md |
scripts/ios_handwritten_report.py |
Generated report; do not hand-edit. |
Hand-authored policy classes are the exception and are explicitly marked. Prefer generated wrappers and focused policy fixes over ad hoc hand wrappers.
- Public package namespace is
net.multigesture.kanama. - Do not reintroduce local workstation paths, private maintainer notes, raw task logs, or obsolete project names into tracked files.
- Keep running task lists, dev logs, bundles, raw logs, and private handoff notes outside the public repo.
- Keep Gradle coordinates, docs snippets, badges, changelog headings, package workflow variables, and companion demo defaults synchronized with the active release pass.
- Some invariants are pinned in more than one file; changing one and not the
others is silent drift. Changing the example script's exported-property set
means updating the count in both
scripts/runtime_smoke.sh(properties size = N) andscripts/local_ci.sh(propertyCount = N). Adding a field to the serialized script model (ScriptModelJson) means bumpingSCRIPT_MODEL_SCHEMA_VERSION. Before finishing, grep the repo for the old value of any pinned constant you touched. - Claims drift the same way constants do, and nothing notices. When you close
a task that removes a limitation, grep for the comments citing it and delete
the ones that are now false. Task 30 gave iOS full wrapper breadth and nobody
grepped for "missing iOS wrapper types"; that line steered decisions for six
more weeks. Where the limitation is machine-checkable, write it as a
KANAMA-BLOCKEDmarker instead —scripts/audit_stale_blockers.pyfails the build when the blocker is lifted. See "Documented Limitations" inCONTRIBUTING.md. - Do not widen ABI-sensitive types to make wrappers generate. Add exact helper shapes and audits first.
- Prefer generated wrappers and focused policy fixes over ad hoc hand wrappers.
- Do not claim platform support unless the matching smoke path has passed.
- Do not treat package zips as proof of exported-game support; desktop kits validate editor/runtime onboarding.
- In GDExtension callbacks, never pre-empt work the engine performs after the
callback returns — path assignment, ResourceCache registration, ownership
transfer. Precedent: issue #106, where the
.ktloader pre-set the script path inside_load, the engine's registeringset_path()early-returned, and every load minted a distinct Script ("cannot assign X to X"). - When adding an engine-facing surface (Script/ScriptLanguage virtual, property
metadata field, loader behavior), pair the Kanama fixture with the equivalent
GDScript construct in a smoke parity probe (the
_kanama_classdb_script_class_smoke/_kanama_typed_global_class_smokepattern), and never leave a virtual returning a constant without a comment arguing why the constant is correct.
| Item | Why it's correct | Do not change |
|---|---|---|
REFCOUNTED_REFERENCE_HASH == REFCOUNTED_UNREFERENCE_HASH (both 2240911060) in ScriptBridge.kt |
reference() and unreference() share the bool() signature, so their method hashes match by construction. |
Verified legitimate against extension_api.json (architecture-review verdict; the review is archived in the internal task repo). |
compatibility_minimum = "4.7" in .gdextension descriptors |
classdb_register_extension_class6 is a Godot 4.7 API; 4.3-4.6 loads would fail at class registration. |
Keep the 4.7 invariant from architecture review F1. |
| Single-class iOS wrapper regeneration can drop methods returning other wrapper types | The generator emits returns only for classes in the active iOS wrapper set. | Regenerate with the full emitted-class union (the iOS "Generator Gotcha" note; recorded in the internal task repo). |
PanamaPort textual remap from java.lang.foreign.* to com.v7878.foreign.* |
It is a pragmatic Android ART compatibility path guarded by pre/post audits. | Do not clean it up without replacing the audit mitigation; see the architecture review Android section. |
Use the source-checkout path for current main or unreleased changes:
./gradlew createStarterProject \
-PkanamaStarterProjectDir=/absolute/path/to/kanama-starter
./gradlew installAddonJar \
-PkanamaProjectDir=/absolute/path/to/kanama-starter \
-PkanamaProjectScriptsDir=/absolute/path/to/kanama-starterFor an existing project, use installStarterTemplate instead of copying a full
starter project over it:
./gradlew installStarterTemplate \
-PkanamaStarterProjectDir=/absolute/path/to/godot_project
./gradlew installAddonJar \
-PkanamaProjectDir=/absolute/path/to/godot_project \
-PkanamaProjectScriptsDir=/absolute/path/to/godot_projectKotlin scripts belong in kotlin-src/, attach with @ScriptClass, and are
compiled through the Godot Build Scripts button or buildScripts.
Build the host desktop kit and local store add-on:
./gradlew packageDistributionsValidate package install flows without a sibling Kanama checkout:
scripts/package_install_smoke.sh \
--desktop-kit \
build/distributions/kanama-desktop-kit-v<version>-<platform>.zip \
/absolute/path/to/godot-4.7-stable
scripts/package_install_smoke.sh \
--store-addon \
build/distributions/kanama-store-addon-v<version>.zip \
/absolute/path/to/godot-4.7-stableDesktop kits are complete starter projects for one platform. Store add-ons are
install-safe addons/ zips for existing projects.
Follow docs/game-dev/porting-gdscript.md and
docs/contributing/demo-porting-rules.md. Preserve scene names, exported
property names, signal names, node paths, upstream gameplay semantics, and
license/attribution notes. Prefer fixing missing Kanama wrapper/runtime support
in this repo over hiding framework gaps in demo code.
Use scripts/generate_api_wrapper.py,
scripts/check_wrapper_generator.py, wrapper audits, and the coverage pages
under docs/contributing/. Public wrappers must be regeneratable unless they are
explicitly hand-authored policy classes.
Regenerate name constants after API refreshes:
python3 scripts/generate_name_constants.pyRefresh wrapper reports before release-facing wrapper changes:
python3 scripts/api_wrapper_generator_report.py --markdown docs/contributing/wrapper-generator-report.md
python3 scripts/api_wrapper_coverage.py --markdown docs/contributing/api-coverage.mdFollow docs/contributing/godot-upgrade.md (the authoritative runbook).
Kanama re-pins only on stable Godot releases. Edit kanamaGodotVersion in
gradle.properties (plus the CI GODOT_VERSION dash form), then run the
mechanical pipeline:
scripts/upgrade_godot.sh /absolute/path/to/new_godot_binaryIt refreshes API/header inputs and Panama bindings, re-adopts all generated wrappers, re-syncs KDoc, refreshes reports, and re-runs the gates — then stops at the human-judgment boundary (device gates, support claims, release wording). Run local CI and demo smoke checks before changing support claims.
Every stable bump: scan for newly deprecated GDExtension functions and keep the
backends converged. 4.7 is the baseline — we do not carry deprecated GDExtension
callbacks once a newer variant solves the same problem. Diff
gdextension/gdextension_interface.h for @deprecated entries, migrate every
backend to the newest variant, and confirm the desktop/Android JVM backend
(src/main) and the iOS Kotlin/Native shim (ios/bootstrap/kanama_ios_shim.c)
bind the same construct/register/script-instance entry points — they pick these
independently and have drifted silently before (construct_object2 vs
construct_object3, issue #91 / task 61). Enforced by
scripts/check_gdextension_modernization.py (in local_ci.sh and the upgrade
gates); steps are in the upgrade runbook.
Android is Supported (4.7 stable), but never widen a support claim past what
the matching APK/emulator or device smoke path has actually validated — the
release floor (Android 13+) and the runtime-only addon caveat both still hold.
Use the Gradle AAR workflow documented in docs/exporting/android.md; do not
infer Android support from desktop package success.
Run the narrowest useful check while iterating, then use the broader gate before publishing or rewriting history:
python3 scripts/validate_godot_api.py --api extension_api.json
python3 scripts/check_wrapper_generator.py
mkdocs build --strict
./gradlew ktfmtFormat # format hand-written Kotlin (ktfmtCheck gates CI)
./gradlew jar
./scripts/local_ci.sh /absolute/path/to/godot-4.7-stableHand-written Kotlin is formatted with ktfmt (googleStyle, 2-space) via the root
build.gradle.kts; local_ci.sh runs ktfmtCheck as a stage. The generated
**/net/multigesture/kanama/api/** wrappers and *.gradle.kts scripts are
excluded — see the Formatting section in CONTRIBUTING.md.
local_ci.sh is the definition of done for processor, runtime, and
script-model changes: it runs the static registrar/count/generated-code checks
that the in-editor runtime smoke does not exercise, so a passing runtime smoke
alone is not sufficient. It also compiles the generated code for every exported
property shape, so run it after widening any accepted @ScriptProperty type.
CI coverage is build-time + desktop runtime only. The PR gate compiles every platform (desktop, the Android runtime AAR, the iOS device xcframework) and runs the desktop/Linux runtime smokes — which also exercise the runtime Android shares via PanamaPort. It does not run on-device: the Android R8-minified runtime gate (the task-42 PanamaPort-under-R8 class) and the iOS device runtime are deliberate local device gates. A green PR means it builds everywhere and the desktop runtime behaves — not that it is device-safe on mobile.
Before a release tag, prefer an isolated clone gate:
./scripts/fresh_clone_smoke.sh /absolute/path/to/godot-4.7-stableThe Web analog is scripts/web_fresh_checkout_smoke.sh: it exports from a clean
clone in an isolated workspace, proves no build-machine path reaches a served
file and that the demo source tree is untouched, then drives the artifact in a
browser with the harness from that clone. See docs/exporting/web.md.
Use demo-repo smoke tasks when a change affects real gameplay ports, wrappers used by demos, package onboarding, or Android exports.