lecodes-sdk 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/README.md +5 -2
  2. package/dist/editor.d.ts +12 -0
  3. package/dist/global.d.ts +4 -8
  4. package/dist/host.d.ts +2 -3
  5. package/dist/types/animate/tween/Animation.d.ts +0 -3
  6. package/dist/types/animate/tween/animateValue.d.ts +4 -2
  7. package/dist/types/animate/tween/easing.d.ts +8 -0
  8. package/dist/types/animate/tween/spec.d.ts +15 -7
  9. package/dist/types/audio/audio.d.ts +2 -1
  10. package/dist/types/canvas/Canvas.d.ts +40 -108
  11. package/dist/types/canvas/gen/cssColor.d.ts +17 -0
  12. package/dist/types/canvas/gen/recorder.d.ts +118 -0
  13. package/dist/types/canvas/gen/spec.d.ts +144 -0
  14. package/dist/types/core/color.d.ts +3 -1
  15. package/dist/types/core/pins.d.ts +18 -0
  16. package/dist/types/g2/Node2D.d.ts +5 -8
  17. package/dist/types/g2/Scene2D.d.ts +6 -2
  18. package/dist/types/gl/Foliage.d.ts +30 -7
  19. package/dist/types/gl/Light.d.ts +11 -0
  20. package/dist/types/gl/Lightmap.d.ts +13 -2
  21. package/dist/types/gl/Material.d.ts +17 -3
  22. package/dist/types/gl/Model.d.ts +6 -2
  23. package/dist/types/gl/Node.d.ts +3 -6
  24. package/dist/types/gl/Particles.d.ts +15 -0
  25. package/dist/types/gl/Scene.d.ts +41 -13
  26. package/dist/types/gl/Texture.d.ts +1 -1
  27. package/dist/types/gl/animation/Locomotion.d.ts +59 -3
  28. package/dist/types/gl/terrain/Terrain.d.ts +8 -0
  29. package/dist/types/inject.d.ts +11 -11
  30. package/dist/types/inject.editor.d.ts +1 -0
  31. package/dist/types/net/core.d.ts +7 -0
  32. package/dist/types/plugin.d.ts +76 -0
  33. package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
  34. package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
  35. package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
  36. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
  37. package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
  38. package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
  39. package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
  40. package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
  41. package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
  42. package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
  43. package/dist/types/runtime/app.d.ts +9 -2
  44. package/dist/types/runtime/fetch.d.ts +2 -0
  45. package/dist/types/runtime/input.d.ts +8 -1
  46. package/dist/types/runtime/media.d.ts +6 -10
  47. package/dist/types/runtime/misc.d.ts +4 -1
  48. package/dist/types/runtime/net.d.ts +3 -2
  49. package/dist/types/runtime/touch.d.ts +32 -0
  50. package/dist/types/scene/defineScene.d.ts +43 -2
  51. package/dist/types/scene/editor.d.ts +52 -0
  52. package/dist/types/scene/gizmos.d.ts +7 -4
  53. package/dist/types/ui/NativeView.d.ts +6 -4
  54. package/dist/types/ui/UI.d.ts +1 -1
  55. package/dist/types/ui/UIBottomSheet.d.ts +6 -12
  56. package/dist/types/ui/UIButton.d.ts +12 -14
  57. package/dist/types/ui/UIContainer.d.ts +0 -6
  58. package/dist/types/ui/UIImage.d.ts +1 -4
  59. package/dist/types/ui/UIInput.d.ts +8 -24
  60. package/dist/types/ui/UIModal.d.ts +0 -2
  61. package/dist/types/ui/UINode.d.ts +70 -56
  62. package/dist/types/ui/UIPager.d.ts +28 -27
  63. package/dist/types/ui/UIPopover.d.ts +0 -2
  64. package/dist/types/ui/UIScreen.d.ts +12 -17
  65. package/dist/types/ui/UIScrollable.d.ts +1 -4
  66. package/dist/types/ui/UIText.d.ts +0 -2
  67. package/dist/types/ui/UIVideo.d.ts +3 -5
  68. package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
  69. package/dist/types/ui/UIWidget.d.ts +10 -9
  70. package/dist/types/ui/colorKeys.gen.d.ts +9 -0
  71. package/dist/types/ui/presentable.d.ts +46 -32
  72. package/dist/types/ui/router.d.ts +18 -7
  73. package/dist/types/ui/styleColor.d.ts +1 -0
  74. package/dist/types/ui/transitions.d.ts +18 -0
  75. package/dist/types/ui/tree.d.ts +75 -0
  76. package/dist/types/version.d.ts +10 -0
  77. package/dist/types.json +1 -1
  78. package/package.json +12 -3
  79. package/prompts/2d.md +2 -6
  80. package/prompts/3d.md +1 -5
  81. package/prompts/README.md +142 -142
  82. package/prompts/canvas.md +9 -8
  83. package/prompts/compose.ts +1 -1
  84. package/prompts/core.md +3 -3
  85. package/prompts/design.md +1 -1
  86. package/prompts/dist/2d-game.md +77 -54
  87. package/prompts/dist/3d-app.md +75 -52
  88. package/prompts/dist/ar-app.md +74 -51
  89. package/prompts/dist/design.md +30 -8
  90. package/prompts/dist/ui-app.md +73 -46
  91. package/prompts/ui-design.md +2 -3
  92. package/prompts/ui.md +45 -37
  93. package/src/animate/tween/Animation.ts +262 -378
  94. package/src/animate/tween/animateValue.ts +103 -100
  95. package/src/animate/tween/easing.ts +10 -3
  96. package/src/animate/tween/spec.ts +505 -479
  97. package/src/audio/Sound.ts +3 -3
  98. package/src/audio/audio.ts +162 -161
  99. package/src/bridges/2d.d.ts +317 -0
  100. package/src/bridges/app.d.ts +91 -0
  101. package/src/bridges/audio.d.ts +97 -0
  102. package/src/bridges/canvas.d.ts +79 -0
  103. package/src/bridges/device.d.ts +72 -0
  104. package/src/bridges/fetch.d.ts +80 -0
  105. package/src/bridges/files.d.ts +70 -0
  106. package/src/bridges/gl.d.ts +1133 -0
  107. package/src/bridges/input.d.ts +72 -0
  108. package/src/bridges/media.d.ts +55 -0
  109. package/src/bridges/nav.d.ts +71 -0
  110. package/src/bridges/net.d.ts +52 -0
  111. package/src/bridges/service.d.ts +52 -0
  112. package/src/bridges/socket.d.ts +31 -0
  113. package/src/bridges/storage.d.ts +33 -0
  114. package/src/bridges/tree.d.ts +301 -0
  115. package/src/bridges/types.d.ts +49 -0
  116. package/src/canvas/Canvas.ts +114 -159
  117. package/src/canvas/gen/cssColor.ts +224 -0
  118. package/src/canvas/gen/recorder.ts +212 -0
  119. package/src/canvas/gen/spec.ts +201 -0
  120. package/src/chisel.ts +193 -0
  121. package/src/compile/assetMacro.ts +1 -1
  122. package/src/compile/bundler.ts +11 -2
  123. package/src/compile/compileProject.ts +43 -4
  124. package/src/compile/fontMacro.ts +3 -4
  125. package/src/compile/header.ts +26 -5
  126. package/src/compile/index.ts +3 -1
  127. package/src/compile/liteMaterial.ts +1 -1
  128. package/src/compile/sceneEditor.ts +11 -26
  129. package/src/core/color.ts +73 -30
  130. package/src/core/pins.ts +51 -0
  131. package/src/core/signals.ts +8 -1
  132. package/src/g2/CharacterController2D.ts +3 -3
  133. package/src/g2/Node2D.ts +57 -39
  134. package/src/g2/Physics2D.ts +2 -2
  135. package/src/g2/Scene2D.ts +35 -23
  136. package/src/g2/Texture2D.ts +1 -1
  137. package/src/g2/loop.ts +4 -4
  138. package/src/gl/Foliage.ts +72 -17
  139. package/src/gl/Geometry.ts +1 -2
  140. package/src/gl/Light.ts +13 -0
  141. package/src/gl/Lightmap.ts +45 -29
  142. package/src/gl/Material.ts +95 -51
  143. package/src/gl/Model.ts +172 -167
  144. package/src/gl/Node.ts +91 -24
  145. package/src/gl/Particles.ts +20 -1
  146. package/src/gl/Scene.ts +100 -44
  147. package/src/gl/Texture.ts +8 -7
  148. package/src/gl/animation/AnimationClip.ts +1 -1
  149. package/src/gl/animation/Locomotion.ts +77 -8
  150. package/src/gl/nav/NavMesh.ts +3 -4
  151. package/src/gl/physics/Physics.ts +2 -2
  152. package/src/gl/physics/physicsEvents.ts +3 -3
  153. package/src/gl/terrain/Terrain.ts +33 -5
  154. package/src/gl/touch.ts +14 -15
  155. package/src/host.d.ts +2 -3
  156. package/src/inject.editor.ts +7 -0
  157. package/src/inject.ts +233 -236
  158. package/src/net/core.ts +6 -5
  159. package/src/net/index.ts +1 -1
  160. package/src/net/replication.ts +1 -1
  161. package/src/plugin.ts +191 -0
  162. package/src/plugins/gen/camera/contract.d.ts +27 -0
  163. package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
  164. package/src/plugins/gen/camera/sdk/camera.ts +57 -0
  165. package/src/plugins/gen/geolocation/contract.d.ts +50 -0
  166. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
  167. package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
  168. package/src/plugins/gen/map/contract.d.ts +144 -0
  169. package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
  170. package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
  171. package/src/plugins/gen/push/contract.d.ts +61 -0
  172. package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
  173. package/src/plugins/gen/push/sdk/push.ts +105 -0
  174. package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
  175. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
  176. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
  177. package/src/plugins/permission.ts +5 -4
  178. package/src/runtime/app.ts +20 -9
  179. package/src/runtime/appEvents.ts +5 -4
  180. package/src/runtime/channel.ts +18 -15
  181. package/src/runtime/clipboard.ts +4 -3
  182. package/src/runtime/datetime.ts +2 -1
  183. package/src/runtime/device.ts +17 -15
  184. package/src/runtime/fetch.ts +30 -20
  185. package/src/runtime/files.ts +16 -15
  186. package/src/runtime/input.ts +21 -8
  187. package/src/runtime/media.ts +50 -46
  188. package/src/runtime/misc.ts +7 -3
  189. package/src/runtime/net.ts +8 -7
  190. package/src/runtime/rpc.ts +1 -3
  191. package/src/runtime/service.ts +19 -14
  192. package/src/runtime/share.ts +4 -3
  193. package/src/runtime/storage.ts +6 -4
  194. package/src/runtime/touch.ts +32 -0
  195. package/src/scene/defineScene.ts +61 -365
  196. package/src/scene/editor.ts +408 -0
  197. package/src/scene/editorPlugins.ts +3 -3
  198. package/src/scene/gizmos.ts +154 -148
  199. package/src/server/db/marci/query.ts +1 -1
  200. package/src/server/host.ts +1 -1
  201. package/src/server/runtime.ts +1 -1
  202. package/src/ui/NativeView.ts +72 -25
  203. package/src/ui/UI.ts +3 -3
  204. package/src/ui/UIBottomSheet.ts +16 -17
  205. package/src/ui/UIButton.ts +54 -16
  206. package/src/ui/UIContainer.ts +0 -6
  207. package/src/ui/UIImage.ts +34 -30
  208. package/src/ui/UIInput.ts +29 -37
  209. package/src/ui/UIModal.ts +1 -3
  210. package/src/ui/UINode.ts +347 -297
  211. package/src/ui/UIPager.ts +93 -78
  212. package/src/ui/UIPopover.ts +0 -2
  213. package/src/ui/UIScreen.ts +41 -36
  214. package/src/ui/UIScrollable.ts +19 -13
  215. package/src/ui/UISpacer.ts +1 -1
  216. package/src/ui/UITabs.ts +8 -6
  217. package/src/ui/UIText.ts +7 -17
  218. package/src/ui/UIVideo.ts +30 -27
  219. package/src/ui/UIVirtualizedList.ts +58 -59
  220. package/src/ui/UIWidget.ts +38 -18
  221. package/src/ui/colorKeys.gen.ts +37 -0
  222. package/src/ui/fonts.ts +2 -2
  223. package/src/ui/presentable.ts +59 -42
  224. package/src/ui/router.ts +52 -35
  225. package/src/ui/styleColor.ts +56 -0
  226. package/src/ui/theme.ts +7 -6
  227. package/src/ui/transitions.ts +249 -0
  228. package/src/ui/tree.ts +346 -0
  229. package/src/version.ts +24 -0
  230. package/tests/helpers/engineWorld.ts +11 -0
  231. package/tests/helpers/fakeTree.ts +353 -0
  232. package/tests/helpers/hostStubs.ts +31 -0
  233. package/tests/helpers/index.ts +14 -0
  234. package/tests/helpers/memoryMarci.ts +124 -0
  235. package/tests/helpers/phases.ts +23 -0
  236. package/tests/helpers/preload.ts +18 -0
  237. package/tests/helpers/stubApp.ts +2 -0
  238. package/tests/helpers/stubDevice.ts +2 -0
  239. package/tests/helpers/stubFetch.ts +2 -0
  240. package/tests/helpers/stubInput.ts +2 -0
  241. package/dist/inject.js +0 -4730
  242. package/dist/types/core/registry.d.ts +0 -7
  243. package/dist/types/plugins/camera.d.ts +0 -25
  244. package/dist/types/plugins/push.d.ts +0 -46
  245. package/src/bridges.d.ts +0 -1760
  246. package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
  247. package/src/compile/__tests__/assetMacro.test.ts +0 -100
  248. package/src/compile/__tests__/assetName.test.ts +0 -55
  249. package/src/compile/__tests__/compile.test.ts +0 -310
  250. package/src/compile/__tests__/detectEntry.test.ts +0 -151
  251. package/src/compile/__tests__/fontMacro.test.ts +0 -199
  252. package/src/compile/__tests__/serverSplit.test.ts +0 -27
  253. package/src/core/__tests__/stateMachine.test.ts +0 -132
  254. package/src/core/registry.ts +0 -23
  255. package/src/plugins/camera.ts +0 -81
  256. package/src/plugins/push.ts +0 -132
  257. package/src/plugins/qr.ts +0 -73
@@ -0,0 +1,1133 @@
1
+ // The 3D engine bridge (`_creator`): engines/gl (Filament) + anim, physics (Jolt), particles, bake.
2
+ // Part of the host contract: every runtime implements this global (runtime/native in C++, the web
3
+ // runtimes in TS). Types are the PRECISE ones the native side reads (see ./types.d.ts); the SDK
4
+ // only sees numbers. The ABI checker (scripts/check-pkg-bridges.mjs) reads this folder.
5
+ import type { bytes, f32, f64, handle, i32, u32, u8 } from "./types"
6
+
7
+ // Object types of the contract (the generator's struct rule: the engine fills a C struct of the same
8
+ // name and fields, the bridge converts it field by field — see scripts/gen-bridges.ts).
9
+ /** One clip of a clip set. */
10
+ export type ClipInfo = { name: string, duration: f32, trackCount: u32 }
11
+ /** One weighted joint of a picked vertex (`pickTriangle`): the joint's name, "#<index>" when it has none. */
12
+ export type PickBone = { name: string, weight: f32 }
13
+ /** One vertex of the picked triangle: `bind` = its position in mesh space, `world` = skinned this frame,
14
+ * `bones` = its JOINTS_0 / WEIGHTS_0 pairs with weight > 0 (empty = unweighted). */
15
+ export type PickVertex = { index: u32, bind: [f32, f32, f32], world: [f32, f32, f32], bones: PickBone[] }
16
+ /** The closest triangle under a screen point (`pickTriangle`). `node` = the mesh node's own entity (0
17
+ * when the host has none), `lightmapGroup` / `uv1` only when the node / primitive carry them. */
18
+ export type TrianglePick = {
19
+ entity: u32, node: u32, nodeName: string, lightmapGroup?: i32,
20
+ mesh: string, primitive: u32, material: string,
21
+ triangle: u32, backface: boolean, distance: f32,
22
+ point: [f32, f32, f32], bary: [f32, f32, f32], uv1?: [f32, f32],
23
+ skin: string, vertices: PickVertex[],
24
+ }
25
+
26
+ declare global {
27
+ // ---- 3D engine (Filament / creator-gl) -------------------------------------------------------
28
+ /** @engine gl */
29
+ var _creator: {
30
+ backend: string
31
+ /** Bring the engine up (or rebuild it) — the deferred start of a bundle whose header says
32
+ * `// gl: defer`; throws when the GPU refuses the shared context. A no-op on a host whose
33
+ * engine is always up. @custom */
34
+ createEngine(): void
35
+
36
+ createScene(): u32
37
+ createOverlayScene(sceneId: u32): u32
38
+ /** @custom */
39
+ createGLView(): void
40
+ /** @custom */
41
+ openScene(sceneId: u32): void
42
+ /** @custom */
43
+ closeScene(): void
44
+ /** @custom */
45
+ launchAR(sceneId: u32, onComplete: () => void, onReject: () => void): void
46
+ /** @custom */
47
+ stopAR(): void
48
+ /** @custom */
49
+ launchVR(sceneId: u32, onComplete: () => void, onReject: (err?: unknown) => void): void
50
+ /** @custom */
51
+ stopVR(): void
52
+ addEntityToScene(sceneId: u32, entityId: u32): void
53
+ removeEntityFromScene(sceneId: u32, entityId: u32): void
54
+ /** @custom */
55
+ warmRender(sceneId: u32, onComplete: () => void): void
56
+ /** Compile every shader variant the scene's materials can need — sun / shadows / fog / skinning
57
+ * and the dynamic-light key — on the backend's compiler threads, then call back. Materials
58
+ * loaded later are queued in the background as they are created. Optional: a host without it
59
+ * resolves at once (the SDK feature-detects). @custom */
60
+ precompileShaders?(sceneId: u32, onComplete: () => void): void
61
+
62
+ /** An OWNED root (owned.h): loose and dropped → its whole subtree goes. @owned GlEntity */
63
+ createEntity(): Handle
64
+ /** @owned GlEntity */
65
+ cloneEntity(entityId: u32): Handle
66
+ /** The subtree, its records, and the SDK hears the ids (the freed channel). @custom */
67
+ destroyEntity(entityId: u32): void
68
+ /** True while the engine holds the entity (a parent, a scene); the SDK unpins a node this says no to. */
69
+ entityIsOwned(entityId: u32): boolean
70
+
71
+ setMatrix(entityId: u32, mat: Float32Array): void
72
+ setWorldMatrix(entityId: u32, mat: Float32Array): void
73
+ getChildCount(entityId: u32): u32
74
+ getChild(entityId: u32, index: i32): u32
75
+
76
+ setPosition(entityId: u32, x: f32, y: f32, z: f32): void
77
+ setQuaternion(entityId: u32, x: f32, y: f32, z: f32, w: f32): void
78
+ setEulerAngles(entityId: u32, x: f32, y: f32, z: f32, order: u8): void
79
+ setScale(entityId: u32, x: f32, y: f32, z: f32): void
80
+
81
+ setParent(entityId: u32, parentEntityId: u32, worldPositionStays: boolean): void
82
+ setParentNull(entityId: u32, worldPositionStays: boolean): void
83
+ getParent(entityId: u32): u32
84
+ /** @custom */
85
+ traverse(entityId: u32, callback: (entityId: number) => void): void
86
+ /** Descendant of `entityId` by name, with the Animator's binding rule: exact name first, then the
87
+ * short name (after the last `:` / `|` — Mixamo `mixamorig:Hips`); a skin joint beats a plain node
88
+ * of the same name (merged GLBs carrying a leftover skeleton copy), otherwise first in tree order.
89
+ * 0 when absent. Optional: hosts that predate it fall back to the SDK's traverse walk (node.bone). */
90
+ findNode?(entityId: u32, name: string): u32
91
+
92
+ /** shadowCascades (2026-09-23, optional 9th arg): 1-4 shadow-map cascades over shadowDistance; a host that predates it
93
+ * renders one. */
94
+ createSunLight(entityId: u32, x: f64, y: f64, z: f64, intensity: f64, color: u32, shadowsQuality: i32, shadowDistance: f64, shadowCascades?: i32): void
95
+ /** Punctual light. `intensity` is luminous POWER in lumens; `falloff` is the metres of
96
+ * influence (filament's own default is 1 m, i.e. invisible, so it is always passed). */
97
+ createPointLight?(entityId: u32, intensity: f64, color: u32, falloff: f64, castShadows: boolean): void
98
+ /** The lightmap bake takes this point light as a RECTANGLE of `width` x `height` metres (an area light: a ceiling
99
+ * panel) in the light's local XZ plane, emitting along its local -Y with the same lumens; 0, 0 = a point again. */
100
+ setLightBakeArea?(entityId: u32, width: f32, height: f32): void
101
+ /** Live intensity for any light (sun: lux, point: lumens) — a flash animates instead of rebuilding. */
102
+ setLightIntensity?(entityId: u32, intensity: f64): void
103
+ setLightColor?(entityId: u32, color: u32): void
104
+ /** `iblFetchId` (optional, -1 = none) names the scene's own probe; without it the host looks for a project-wide `ibl.ktx`. @custom */
105
+ setDefaultIbl(sceneId: u32, intensity: f64, iblFetchId?: i32): void
106
+ /** The environment's intensity in lux, live (`scene.environmentIntensity`) — `setDefaultIbl` is
107
+ * the only other way to change it and it rebuilds the cubemap and the IndirectLight from the
108
+ * ktx every call, so it can never carry a slider. No-op until the scene has an IBL. Optional:
109
+ * hosts that predate it keep whatever `setDefaultIbl` set at open. */
110
+ setEnvironmentIntensity?(sceneId: u32, intensity: f32): void
111
+ setBloomOptions(sceneId: u32, enabled: boolean, strength: f32, quality: u8): void
112
+ setToneMapping?(sceneId: u32, mode: u8): void
113
+ /** Exposure of the scene's camera — filament's physical model: `aperture` f-stops,
114
+ * `shutterSpeed` seconds, `sensitivity` ISO (default f/16, 1/125 s, ISO 100 = EV100 15, bright
115
+ * sunlight). ISO ×2 = one stop brighter. Scene-referred light follows it; particle emission is
116
+ * post-exposure and does not. Optional: hosts that predate it keep the fixed default. */
117
+ setCameraExposure?(sceneId: u32, aperture: f32, shutterSpeed: f32, sensitivity: f32): void
118
+ /** Display output range (macOS/iOS EDR, `device.hdr`). `getDisplayHeadroom` is what the engine
119
+ * renders to right now: the screen's peak as a multiple of SDR white, quantised to half-stops;
120
+ * 0 or 1 = SDR. Live — it ramps up from 1 over the first seconds, follows brightness and the
121
+ * screen under the window. `getDisplayMaxHeadroom` is the peak the surface can reach at all
122
+ * (1 = SDR), constant once the surface exists: the value to branch content on. Optional: hosts
123
+ * that predate it, or render 8-bit, are SDR. */
124
+ getDisplayHeadroom?(): f64
125
+ /** @c getDisplayHeadroom */
126
+ getDisplayMaxHeadroom?(): f64
127
+ /** HDR look, engine-wide (creator.h `setHdrStrength` / `setHdrPaperWhite`): `strength` 0..1 =
128
+ * how much of the picture reaches for the headroom (0 only what SDR clipped, 1 nearly
129
+ * everything), `paperWhite` 1..8 = where the operator's white lands as a multiple of SDR white
130
+ * (the "HDR brightness" of a console calibration screen; clamped to the headroom). A host may
131
+ * pin either from its environment — the getters report what is in force. Optional. */
132
+ setHdrStrength?(strength: f32): void
133
+ getHdrStrength?(): f64
134
+ setHdrPaperWhite?(paperWhite: f32): void
135
+ getHdrPaperWhite?(): f64
136
+ /** Screen-space ambient occlusion: the contact darkening in creases and where objects meet the
137
+ * ground. `radius` is world-space metres, `power` the falloff contrast, `quality` 0 LOW … 3
138
+ * ULTRA (sample count — not the buffer resolution, which stays half-res).
139
+ * Optional: hosts that predate it render without AO and the SDK skips the call. */
140
+ setAmbientOcclusionOptions?(sceneId: u32, enabled: boolean, intensity: f32, radius: f32, power: f32, quality: u8): void
141
+ /** Distance fog / aerial perspective. `color` is packed 0xRRGGBB used as a TINT on the
142
+ * in-scattered ambient — the engine multiplies it by the environment luminance, so white means
143
+ * "as bright as the ambient" and it is NOT the absolute-radiance convention `setSkybox` uses.
144
+ * `density` = extinction per metre at `height`, `heightFalloff` 1/m (0 = uniform),
145
+ * `cutOff` <= 0 = apply at every distance (the skybox included — that is what blends the
146
+ * horizon), `fromIbl` = take the colour from the environment in the view direction and tint
147
+ * it by `color`.
148
+ * Optional: hosts that predate it render without fog and the SDK skips the call. */
149
+ setFogOptions?(sceneId: u32, enabled: boolean, color: u32, distance: f32, density: f32, height: f32, heightFalloff: f32, maxOpacity: f32, cutOff: f32, fromIbl: boolean): void
150
+ setSkybox(sceneId: u32, color: u32): void
151
+ /** Draw the scene's IBL environment as the sky instead of a flat colour. No-op until the IBL
152
+ * exists (setDefaultIbl runs first). Optional: hosts that predate it keep the flat skybox. */
153
+ setSkyboxFromEnvironment?(sceneId: u32): void
154
+ /** The sky from its own KTX1 cubemap. `fetchId` is a local-resource id (`_creatorFetch.local`
155
+ * / an `asset()` handle), pointing at cmgen's `<name>_skybox.ktx` — the sharp single-mip file,
156
+ * not the roughness-prefiltered `_ibl.ktx` beside it. Optional: hosts that predate it keep the
157
+ * flat skybox. */
158
+ setSkyboxTexture?(sceneId: u32, fetchId: bytes): void
159
+ /** Temporal anti-aliasing (Filament's TAA) for the scene and its overlays: on, MSAA and FXAA are off for the
160
+ * view; `feedback` = the history's share (0.12 default, higher = less ghosting, less smoothing), `sharpness` =
161
+ * the post-TAA sharpen (0–1), `upscale` = the fraction the 3D is rendered at and TAA-upscaled from (0.5–1,
162
+ * 1 = none; below 1 Filament's dynamic resolution is off), `nearLimit` = the band in front of the camera (m,
163
+ * 0 = off) whose pixels keep their screen position instead of the camera's reprojection — a first-person
164
+ * viewmodel, which would trail on every turn otherwise. Off restores the scene's MSAA state. Optional: a
165
+ * host without it keeps MSAA. @c setTemporalAntiAliasing */
166
+ setTemporalAntiAliasing?(sceneId: u32, enabled: boolean, feedback: f32, sharpness: f32, upscale: f32, nearLimit: f32): void
167
+ /** @c setMultiSampleAntiAliasing */
168
+ setSceneMultiSampleAntiAliasing(sceneId: u32, enabled: boolean, scale: u8): void
169
+ /** Filament View::setStencilBufferEnabled (Scene.stencil). Optional. */
170
+ setSceneStencil?(sceneId: u32, enabled: boolean): void
171
+ /** Render resolution: `renderScale` (0.25–1) = fixed 3D-buffer scale vs the viewport (the UI is
172
+ * untouched; a host that owns the 3D texture resizes it, one rendering into the swapchain may
173
+ * ignore it), `dynamicResolution` = engine-adaptive scaling under that down to `minScale`.
174
+ * Optional: older hosts predate it and the SDK skips the call. */
175
+ setSceneRenderOptions?(sceneId: u32, renderScale: f32, dynamicResolution: boolean, minScale: f32): void
176
+ /** Anisotropic filtering for every texture bound FROM HERE ON (1 = off, 2 = the engine default,
177
+ * 16 = max; clamped). Engine-wide rather than per scene, because a sampler is baked when its
178
+ * texture is bound — a glTF binds during `Model.load`, and two scenes cannot disagree about a
179
+ * texture they share. Call it before loading the assets it should apply to; `SceneOptions.
180
+ * anisotropy` does that automatically (a scene's env runs before its nodes build). Optional:
181
+ * hosts that predate it keep isotropic filtering and the SDK skips the call. */
182
+ setTextureAnisotropy?(level: f32): void
183
+ /** Engine-wide cap on texture size (`Texture.maxSize` / `SceneOptions.maxTextureSize`): a
184
+ * KTX2 wider or taller than `size` loses its top mip levels on load, a glTF PNG/JPEG is
185
+ * downsampled; 0 = no cap. Reaches textures created AFTER the call — a loaded level keeps
186
+ * its textures, so a settings menu applies it on the next level load. `createTexture` flag
187
+ * 2 (FULL_SIZE) exempts one texture (lightmap pages). Optional: hosts that predate it load
188
+ * full-size textures and the SDK skips the call. */
189
+ setTextureMaxSize?(size: u32): void
190
+ /** Depth-reading effects on / off (`scene.setDepthEffects`): soft particles (`depthFade`) and
191
+ * projected decals read the scene depth, which costs a half-res depth pre-pass of every opaque
192
+ * draw. Off = no pre-pass, hard-edged particles, decal sets draw nothing. Engine-wide, live.
193
+ * Optional: hosts that predate it keep the effects and the SDK skips the call. */
194
+ setDepthEffects?(enabled: boolean): void
195
+ /** LOD distance (`scene.setLodBias`): the LOD pass' screen-size thresholds × `bias` — 2 = every
196
+ * level switches at half the distance, 0.5 = full detail twice as far; 1 = the defaults
197
+ * (0.30 / 0.12 / 0.05 of the viewport height). Engine-wide, live, clamped 0.25..8. Optional:
198
+ * hosts without the LOD pass ignore it and the SDK skips the call. */
199
+ setLodBias?(bias: f64): void
200
+ setMaterialGlobalParameter(sceneId: u32, i: u32, x: f32, y: f32, z: f32, w: f32): void
201
+ getCameraFov(sceneId: u32, fovType: u8): f64
202
+ /** The scene camera's projection: VERTICAL fov in degrees + near/far clip distances (defaults
203
+ * 60 / 0.01 / 1000). Aspect stays host-owned — the host STORES these per scene and re-applies
204
+ * them whenever the viewport changes, so one call outlives every resize. Optional: hosts that
205
+ * predate it keep the fixed defaults and the SDK feature-detects (Camera.setProjection). */
206
+ setCameraProjection?(sceneId: u32, fov: f32, near: f32, far: f32): void
207
+
208
+ /** @into mat 16 */
209
+ getMatrix(entityId: u32, mat: Float32Array): void
210
+ /** @into mat 16 */
211
+ getWorldMatrix(entityId: u32, mat: Float32Array): void
212
+ /** @into mat 3 */
213
+ getWorldPosition(entityId: u32, mat: Float32Array): void
214
+ /** @into mat 3 */
215
+ getWorldDirection(entityId: u32, mode: u8, mat: Float32Array): void
216
+
217
+ /** @into mat 16 */
218
+ getWorldMatrixInverse(entityId: u32, mat: Float32Array): void
219
+ /** Screen inputs are LOGICAL px: the web host scales them to its backing store by hand. @into mat 3 @custom web */
220
+ getCameraViewDirection(sceneId: u32, screenX: f32, screenY: f32, mat: Float32Array): void
221
+
222
+ /** `uv1` (optional, 2 floats per vertex) is the lightmap UV set (Geometry.uv1); absent → the
223
+ * host duplicates `uv` into UV1. Hosts that predate the argument ignore it. */
224
+ /** `colors` (Geometry.colors) is 4 bytes RGBA per vertex for a `requires: [color]` material;
225
+ * a host that predates it draws the mesh white. @c createMeshC @count verticesCount=vertices/3 indicesCount=indices */
226
+ setMesh(entityId: u32, materialId: u32, vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array, meshType: i32, uv1?: Float32Array, colors?: Uint8Array): void
227
+ // Terrain (gl/Terrain.ts ↔ creator-gl/src/terrain.cpp, docs/terrain-plan.md §1.4): a heightmap grid of
228
+ // sizeX × sizeZ samples `cellSize` apart (+X across columns, +Z across rows, height on +Y, sample 0 at
229
+ // the node origin), drawn as one renderable per `chunk`×`chunk` cells on internal children of the
230
+ // entity (u16 indices → chunk ≤ 255). `holes` = one byte per sample, 1 = hole (a triangle exists only
231
+ // when its three samples are valid — the same rule as the Jolt height field), null = none. UV0 = local
232
+ // XZ in metres, UV1 = the terrain's unit square. Returns the chunk count (0 = refused). terrainUpdate
233
+ // re-reads the FULL arrays and rebuilds the chunks the sample rectangle touches. All optional: a host
234
+ // without them gets the SDK's own chunk meshes through setMesh (gl/terrainMesh.ts).
235
+ terrainCreate?(entityId: u32, materialId: u32, heights: Float32Array, sizeX: u32, sizeZ: u32, cellSize: f32, chunk: u32, holes: Uint8Array | null): u32
236
+ terrainUpdate?(entityId: u32, heights: Float32Array, holes: Uint8Array | null, x0: u32, z0: u32, w: u32, h: u32): void
237
+ terrainSetMaterial?(entityId: u32, materialId: u32): void
238
+ terrainSetShadows?(entityId: u32, cast: boolean, receive: boolean): void
239
+ // Instanced mesh (gl/InstancedMesh.ts ↔ creator-gl/src/instanced.cpp): `count` transforms,
240
+ // column-major 4×4 local to the node, all-zero = hidden. setInstanceTransforms writes
241
+ // matrices.length / 16 of them starting at `first` (a subarray view is fine — consumed synchronously).
242
+ /** @count verticesCount=vertices/3 indicesCount=indices */
243
+ createInstancedMesh(entityId: u32, materialId: u32, vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array, count: u32): void
244
+ /** @count count=matrices/16 */
245
+ setInstanceTransforms(entityId: u32, matrices: Float32Array, first: u32): void
246
+ setInstancedMeshMaterial(entityId: u32, materialId: u32): void
247
+ setInstancedMeshShadows(entityId: u32, cast: boolean, receive: boolean): void
248
+ setCulling(entityId: u32, culling: boolean): void
249
+ /** Filament RenderableManager::setPriority — the coarse draw order, 0..7 (Mesh.renderPriority).
250
+ * Optional: an older host leaves everything at the default 4. */
251
+ setRenderPriority?(entityId: u32, priority: u32): void
252
+ /** Filament MaterialInstance::setDepthCulling / setDepthWrite — per-instance overrides of the
253
+ * depth state baked into the shader package (Material.depthTest / depthWrite). Optional. */
254
+ setMaterialDepthTest?(materialInstanceId: u32, enable: boolean): void
255
+ setMaterialDepthWrite?(materialInstanceId: u32, enable: boolean): void
256
+ /** Filament MaterialInstance::setCullingMode: 0 none (double-sided), 1 front, 2 back. Optional. */
257
+ setMaterialCulling?(materialInstanceId: u32, mode: u8): void
258
+ /** Filament MaterialInstance stencil state in one call (Material.stencil): `test` 0 always, 1 never,
259
+ * 2 less, 3 lessEqual, 4 greater, 5 greaterEqual, 6 equal, 7 notEqual; the ops 0 keep, 1 zero,
260
+ * 2 replace, 3 increment, 4 decrement, 5 invert. Optional. */
261
+ setMaterialStencil?(materialInstanceId: u32, write: boolean, test: u8, ref: u8, onPass: u8, onFail: u8, onDepthFail: u8, readMask: u8, writeMask: u8): void
262
+ setCastShadows(entityId: u32, culling: boolean): void
263
+ setReceiveShadows(entityId: u32, culling: boolean): void
264
+
265
+ /** Decode a fetched image into a texture. `flags` (optional, Texture.load): bit 1 = LINEAR data
266
+ * (a normal map — store RGBA8, not sRGB); unset / absent = colour, sRGB. A host that ignores
267
+ * it loads colour correctly and normal maps wrongly. KTX2 decides by its own header. @custom */
268
+ createTexture(systemId: bytes, onComplete: (texture: Handle, width: number, height: number) => void, onReject: () => void, flags?: u32): void
269
+ // Texture from a baked _creatorCanvas surface (RGBA8, already rasterized — synchronous, no decode).
270
+ /** @custom */
271
+ createTextureFromCanvas(surfaceId: i32): u32
272
+ /** @custom */
273
+ updateTextureFromCanvas(texId: u32, surfaceId: i32): void
274
+ /** A texture from raw UBYTE pixels — `channels` 1–4, row-major, `width*height*channels` bytes, no
275
+ * mips; `srgb` selects the sRGB internal format for 3/4 channels (colour) vs linear (data — a
276
+ * terrain's control map). The bytes are copied. Returns 0xFFFFFFFF on failure. `updateTexturePixels`
277
+ * re-uploads a sub-rectangle with the creation channel count. Optional (Texture.fromPixels throws). @args data width height channels srgb @count pixelBytes=data @owned GlTexture */
278
+ createTexturePixels?(width: u32, height: u32, channels: u32, data: Uint8Array, srgb: boolean): Handle
279
+ updateTexturePixels?(textureId: u32, x: u32, y: u32, width: u32, height: u32, data: Uint8Array): void
280
+
281
+ /** A material INSTANCE of these .filamat bytes (an app's own shader, fetched); the material itself is deduplicated by content in the engine. @c createMaterialInstanceFromBytes @owned GlMaterial @throw createMaterial: no .filamat bytes behind this fetch id */
282
+ createMaterial(systemId: bytes): Handle
283
+ /** A material INSTANCE of a BUILT-IN material by name — `"lit"`, `"unlit-transparent"`, `"lightmap-masked"` …:
284
+ * the sources of engines/gl/materials — or null when this host carries none of that name. The engine's
285
+ * materials are the runtime's to find, like the ubershader archive: in the materials archive the host
286
+ * hands over (`HostFetch.systemBuffer(3)`), on every host; one material per name is kept. The names a
287
+ * bundle asks for with a string literal are in its `// preload:` header (`material:<name>`) — what a
288
+ * host that fetches its archives by need goes by. @custom */
289
+ builtinMaterial(name: string): Handle | null
290
+ setMaterial(entityId: u32, materialId: u32, index: i32): void
291
+ getMaterial(entityId: u32, index: i32): u32
292
+
293
+ setUniformRgb(materialId: u32, uniform: string, color: u32): void
294
+ setUniformRgba(materialId: u32, uniform: string, color: u32): void
295
+ setUniformFloat(materialId: u32, uniform: string, value: f64): void
296
+ setUniformBoolean(materialId: u32, uniform: string, value: boolean): void
297
+ setUniformTexture(materialId: u32, uniform: string, systemId: u32, wrapS: u8, wrapT: u8): void
298
+ setUniformArray(materialId: u32, uniform: string, value: Float32Array): void
299
+ setUniformNull(materialId: u32, uniform: string): void
300
+
301
+ /** @custom */
302
+ attachCameraToAR(sceneId: u32, cameraEntityId: u32, onProjectionChange: (mat: Float32Array, fov: number) => void): void
303
+ /** @custom */
304
+ getDisplaySize(): Float32Array
305
+
306
+ setVisible(entityId: u32, visible: boolean): void
307
+ isVisible(entityId: u32): boolean
308
+
309
+ /** @custom */
310
+ createGlb(systemId: bytes, onComplete: (root: Handle | null) => void, onReject: () => void): void
311
+ setGlbCulling(entityId: u32, culling: boolean): void
312
+ /** Optional. True when the host refits a skinned model's frustum-culling bounds to its joints every
313
+ * frame (creator-gl animation/skinning.cpp), i.e. culling is safe for animated GLBs. Model.load
314
+ * defaults culling ON only where this returns true; absent / false = the always-draw default. */
315
+ skinnedCullingSupported?(): boolean
316
+ /** Optional. Level-of-detail override for a GLB instance (docs/lod-plan.md): `mesh` / `anim` are -1 for
317
+ * automatic (the engine picks by screen size and visibility) or a forced level 0..3. Backs Model.lod
318
+ * and Animator.lod; a host without it (no LOD pass) leaves everything at full detail. */
319
+ setLod?(entityId: u32, mesh: f32, anim: f32): void
320
+
321
+ // Render-synced aspect update(dt) dispatch, called from inside render(). Early = before the physics
322
+ // step; late = after animations/particles, just before draw. Single-slot per phase (the SDK's Aspect
323
+ // dispatcher registers one callback per phase that iterates its ordered updater list).
324
+ /** @custom */
325
+ setEarlyUpdate(cb: (dt: number) => void): void
326
+ /** @custom */
327
+ setLateUpdate(cb: (dt: number) => void): void
328
+ /** FIXED phase - the SDK's `updateFixed`: once per physics substep with dt = 1/60 exactly, BEFORE
329
+ * that substep's Jolt step (move commands / velocities written here feed the same step). Under
330
+ * `setTimeScale` the NUMBER of substeps changes, never the dt; at most 4 per frame. `null` clears it -
331
+ * registered lazily, only once an aspect declares the phase (one flag check per substep otherwise).
332
+ * Optional: a host without it gets the SDK's own 1/60 accumulator inside the early phase. @custom */
333
+ setFixedUpdate?(cb: ((dt: number) => void) | null): void
334
+ /** The engine's clock multiplier for physics / animators / particles (1 normal, 0 frozen) — the
335
+ * SDK's `Time.scale` / `Time.paused` pushed down so the sims stay in step with game code. The
336
+ * two aspect phases still receive the RAW wall-clock dt (the SDK scales it itself). Optional:
337
+ * a host without it keeps its sims at wall-clock speed. */
338
+ setTimeScale?(scale: f64): void
339
+ // Physics contact/sensor events (drained after each step): a/b = entity ids, type 0 enter / 1 exit.
340
+ /** @custom */
341
+ setOnPhysicsEvent(cb: (a: number, b: number, type: number) => void): void
342
+
343
+ // ---- Animation system (docs/animation-v2-plan.md): AnimationClip + Animator — THE skeletal animation
344
+ // path, evaluated by creator-anim on every host. A clip set is parsed straight from GLB bytes (tracks
345
+ // target bone NAMES, no entities); a model's embedded clips are registered by createGlb (getGlbClipSet,
346
+ // 0 = none). An Animator binds clips to one node hierarchy by name; the native evaluator runs the WHOLE
347
+ // per-frame loop (sources, transitions, blend spaces, one-shot hand-over, root motion) — the SDK only
348
+ // issues play/stop/blend calls and has no tick. Skinning is flushed AFTER the late phase so IK /
349
+ // procedural joint writes land in the skin.
350
+ // TRANSITIONS ARE INERTIAL, NOT CROSSFADES: a layer evaluates exactly ONE source (its one-shot, its
351
+ // loop, or nothing); a switch records the difference between the pose the layer SHOWED and the pose the
352
+ // new source shows and decays it away over `fade` seconds (halflife = 0.4 × fade). A replaced source is
353
+ // silent from that moment — it costs nothing, its weight is 0 at once, and it may be replaced again
354
+ // mid-transition (the offset is re-recorded from the displayed pose). `fade` 0 = cut.
355
+ /** @custom */
356
+ loadClips(systemId: bytes, onComplete: (clipSetId: number) => void, onReject: (err: unknown) => void): void
357
+ // names = newline-joined track targets; data = [trackCount, duration (<=0 → max key time), (path 0 T/1 R/2 S,
358
+ // interp 0 linear/1 step/2 cubic, comps, keyCount, times…, values…)*]. Returns a one-clip set id (0 = bad).
359
+ createClipFromTracks(names: string, data: Float32Array): u32
360
+ /** @c getClipInfos */
361
+ getClipSetInfo(clipSetId: u32): ClipInfo[]
362
+ // A clip DERIVED from one of the set as a NEW single-clip set (index 0) — AnimationClip.from(clip, { mirror,
363
+ // from, to }): the mirror (left ↔ right on the set's own rig, the GLB's node tree: contacts swapped, heading
364
+ // negated), then the [start, end] window re-timed to 0 (start < 0 = whole, end < 0 = the clip's end; boundary
365
+ // values interpolated in, events re-timed). 0 = bad id / range, or a mirror asked of a set without a rig.
366
+ deriveClip(clipSetId: u32, clip: u32, mirror: boolean, start: f32, end: f32): u32
367
+ // Clip events on the CLIP: normalized times (sorted ascending). Every slot bound to the clip, in every
368
+ // animator, fires slot event type 4 + i on crossing event i. Empty = clear.
369
+ setClipEvents(clipSetId: u32, clip: u32, times: Float32Array): void
370
+ getGlbClipSet(entityId: u32): u32
371
+ destroyClipSet(clipSetId: u32): void
372
+ animatorCreate(entityId: u32): u32 // 0 = not a transform node
373
+ animatorDestroy(animatorId: u32): void // leaves the skeleton in rest pose
374
+ // One (clip, layer) slot, silent until played / made a blend member. slot index or -1.
375
+ animatorBind(animatorId: u32, clipSetId: u32, clipIndex: u32, layer: u8): i32
376
+ animatorBoundTracks(animatorId: u32, slot: u32): u32
377
+ // The layer's LOOP: member slots + positions (dims 1: x per member, 2: x,y; one member = a plain looping
378
+ // clip). The members share ONE cycle clock, each placed on it LINEARLY by `phases` — two floats per member,
379
+ // (offset, cycles): φ(t) = offset + cycles · t / duration, 0 at a left-foot-down (measured offline
380
+ // from the clip's foot marks). An empty array, or cycles <= 0 for a member = normalized time (0, 1) and no
381
+ // cycle to phase-match to — the clock never reads the clip's marks. animatorSetBlendValue picks the mix (1D linear between neighbours /
382
+ // 2D gradient band). Setting it takes the layer over from whatever it shows — the previous loop and any
383
+ // one-shot on it go silent — with a transition of `fade`. Empty = no loop. speed = the members' rate.
384
+ /** @args animatorId layer dims slots positions phases fade speed */
385
+ animatorSetBlend(animatorId: u32, layer: u8, dims: 1 | 2, slots: Uint16Array, positions: Float32Array, fade: f32, speed: f32, phases: Float32Array): void
386
+ animatorSetBlendValue(animatorId: u32, layer: u8, x: f32, y: f32): void
387
+ // Play a slot as the layer's one-shot, transitioned in over fadeIn (0 = cut) from whatever the layer
388
+ // showed (playing a blend MEMBER brings the whole blend back instead). A non-looping one-shot plays to its
389
+ // end and hands the layer back to the loop THERE, the return transition taking fadeOut from its last pose
390
+ // (0 = cut back; on a loopless layer hold the last frame).
391
+ // restart = rewind even if already playing (a restart is a new source: it transitions from the pose shown).
392
+ animatorPlay(animatorId: u32, slot: u32, loop: boolean, speed: f32, fadeIn: f32, fadeOut: f32, restart: boolean): void
393
+ // TURN WARP for the slot's current play (right after animatorPlay): the node's turn from the clip's baked heading is
394
+ // scaled to `radians` total; the pose keeps its own turn, the extra pivots about the planted foot. enabled false =
395
+ // the clip's own turn. Optional: older hosts lack it.
396
+ animatorSlotSetTurn?(animatorId: u32, slot: u32, radians: f32, enabled: boolean): void
397
+ // WINDOW of the slot's current play (right after animatorPlay): the one-shot runs [start, end] clip seconds — enters
398
+ // at start (at end when backwards) if the play rewound, completes + hands over at end, clip events outside never fire.
399
+ // end < 0 = the clip's end. Loops ignore it. Optional: older hosts lack it (the whole clip plays).
400
+ animatorSlotSetWindow?(animatorId: u32, slot: u32, start: f32, end: f32): void
401
+ // Release over `fade` (a transition toward what is left — the loop, or the rest pose): one slot; a
402
+ // layer's one-shot + loop (slot -1); everything (layer -1).
403
+ animatorStop(animatorId: u32, layer: i32, slot: i32, fade: f32): void
404
+ animatorSeek(animatorId: u32, slot: u32, time: f32): void // seeking a blend member moves the blend
405
+ animatorGetSlotTime(animatorId: u32, slot: u32): f64
406
+ animatorGetSlotWeight(animatorId: u32, slot: u32): f64 // 1 = the layer's source, a loop member = its share, else 0
407
+ // Layer config: weight 0–1; additive = each slot's DELTA vs its clip's first frame on top of the layers
408
+ // below; maskRoot = bone name(s, '\n'-separated) whose subtrees the layer drives ("" = all).
409
+ animatorSetLayer(animatorId: u32, layer: u8, weight: f32, additive: boolean, maskRoot: string): void
410
+ // ANTICIPATION for the layer's next source change: the transition starts with -amount x the new source's
411
+ // joint velocity (a wind-up against the coming motion), consumed by that switch. Optional: older hosts lack it.
412
+ animatorSetLayerAnticipation?(animatorId: u32, layer: u8, amount: f32): void
413
+ animatorSetGlobal(animatorId: u32, speed: f32, paused: boolean): void
414
+ // Root motion: "" off, "*" auto (the shallowest joint a base-layer clip translates), else a bone name. The
415
+ // root's horizontal travel (animator-node frame) is stripped from the pose and, per apply: 0 accumulated
416
+ // only (animatorGetRootMotion copies + clears out[3]), 1 added to the node's transform, 2 fed to the
417
+ // CharacterController on the node or an ancestor as a world velocity (falls back to 1 without one).
418
+ /** `apply` bits 0–1: 0 accumulate only / 1 move the node / 2 feed the CharacterController; bit 2 (+4): the
419
+ * root joint's yaw about the node's up is root motion too (off the pose, onto the node the travel lands on). */
420
+ animatorSetRootMotion(animatorId: u32, bone: string, apply: u8): void
421
+ /** @into out 3 */
422
+ animatorGetRootMotion(animatorId: u32, out: Float32Array): void
423
+ // Slot events: 0 completed (non-loop end) / 1 loop wrapped / 2 settled (no longer a source, after a stop
424
+ // or a replacement) / 3 HAND-OVER (a one-shot's return starts — for the SDK the clip is over; what the app
425
+ // starts in response takes the layer over instead) / 4+i clip event i. Single-slot (routed by animatorId).
426
+ /** @custom */
427
+ setOnAnimatorEvent(callback: (animatorId: number, slot: number, type: number) => void): void
428
+ // ---- contacts, phase, root curves (docs/animation-v2-plan.md §2.5–2.6) ----
429
+ // Every clip bound to a skeleton is baked once against it: when each foot is planted, the gait phase
430
+ // φ(t) (0 at a left-foot-down, 0.5 at a right-foot-down, unwrapped over the clip; absent for a clip
431
+ // with no gait cycle) and the root's cumulative travel / yaw / speed. A controller asks these instead
432
+ // of shipping measured tables of its own.
433
+ // The feet: '\n'-joined bone names per side (foot[, toe/ball]); both "" = classify by name. Re-bakes.
434
+ animatorSetFeet(animatorId: u32, left: string, right: string): void
435
+ // One curve of a slot's clip at `time` seconds (< 0 = the slot's clock now): which 0 φ (-1 = no gait)
436
+ // / 1 travel (m) / 2 yaw (rad, + = left) / 3 speed (m/s) / 4-5 unit travel direction x / z
437
+ // (model space, held through stills — integrate dir × d(travel) for the root's 2D path).
438
+ animatorSlotCurveAt(animatorId: u32, slot: u32, which: 0 | 1 | 2 | 3 | 4 | 5 | 6, time: f32): f64
439
+ // out ← [curve sample dt, total travel (m), mean speed (m/s), in-place flag]. False = unbound slot.
440
+ animatorSlotCurveInfo(animatorId: u32, slot: u32, out: Float32Array): boolean
441
+ animatorSlotTurn(animatorId: u32, slot: u32): f64 // the clip's total root yaw, rad
442
+ // The first time the clip has turned `yaw` radians — where a turn is entered by a body already
443
+ // that far into the same turn, so the two read as one move.
444
+ animatorSlotTimeAtTurn(animatorId: u32, slot: u32, yaw: f32): f64
445
+ // What the LAYER shows, as a cycle phase in [0, 1) — its loop's clock, or its one-shot's φ; -1 = none.
446
+ animatorLayerPhase(animatorId: u32, layer: u8): f64
447
+ // Seek the slot to the first time whose φ ≡ phase (mod 1); a blend member moves its whole group.
448
+ animatorSeekPhase(animatorId: u32, slot: u32, phase: f32): void
449
+ // How far the slot's pose is from `target`'s at the same cycle phase, metres (joint distance +
450
+ // the velocity difference over 0.1 s, the planted foot weighted most) — what handing over to that
451
+ // clip would hand the inertializer. Fills `out` at the curve rate, returns the sample count.
452
+ // align: 0 = compare at the same cycle phase, 1 = at the same time (two clips that both begin
453
+ // from standing have no shared cycle to line up on).
454
+ animatorSlotFit(animatorId: u32, slot: u32, target: u32, align: 0 | 1, out: Float32Array): u32
455
+ // The earliest time in the slot where that hand-over costs no more than `tolerance` metres;
456
+ // atContact snaps to the next foot-down at or after it. -1 = never that close.
457
+ animatorSlotExit(animatorId: u32, slot: u32, target: u32, tolerance: f32, atContact: boolean, align: 0 | 1): f64
458
+ // CYCLE ALIGNMENT by pose, no marks: given slot a's cycle (offA, cyclesA), the (offset, cycles) of slot b
459
+ // under which the two loops show the same pose at the same gait phase — b's cycle count searched over
460
+ // cyclesA × {1/3 … 3}, its offset on a fine grid. out = [offset, cycles, score (metres), margin (runner-up
461
+ // ≥ 0.2 cycle away minus the best; ~0 = ambiguous)]. Returns 1, or 0 when there is nothing to compare.
462
+ animatorSlotAlign(animatorId: u32, a: u32, b: u32, offA: f32, cyclesA: f32, out: Float32Array): i32
463
+ // Contact spans as (side 0 left / 1 right, from, to, atX, atY, atZ) sextuplets (seconds, model space);
464
+ // returns the span count, filling `out` up to its capacity.
465
+ /** @count maxSpans=out/8 */
466
+ animatorSlotContacts(animatorId: u32, slot: u32, out: Float32Array): u32
467
+ // A CLIMBING clip's tread levels (model-space plant heights, sorted ascending): out[0] = the
468
+ // riser (median level spacing), out[1..] = the levels, filled up to out's capacity. Returns the
469
+ // level count — 0 for a flat clip.
470
+ animatorSlotTreads(animatorId: u32, slot: u32, out: Float32Array): u32
471
+ // The clip's baked physics at `time` seconds (< 0 = the slot's current time), unit body mass,
472
+ // model space: out11 = COM position xyz, COM velocity xyz (= linear momentum per kg), angular
473
+ // momentum about the COM xyz, then per-foot support left/right (contact-gated, seesaw split,
474
+ // scaled by the vertical force proxy — > 1 on a landing, 0 in flight). 0 = no body segments
475
+ // classified on this skeleton.
476
+ animatorSlotPhysics(animatorId: u32, slot: u32, time: f32, out: Float32Array): i32
477
+ // The clip's MATCHING FEATURE ROW at `time` (37 floats, the clip's heading frame at that time —
478
+ // x lateral (+ left), y up, z forward): 0–5 feet positions (relative to the pelvis' ground
479
+ // point), 6–11 feet velocities, 12–14 pelvis velocity, 15 pelvis height, 16–18 COM velocity,
480
+ // 19–20 support L/R, 21–22 contact phase L/R, 23 yaw angular momentum, 24–31 the clip's own
481
+ // path 0.3/0.6/1.0/1.5 s ahead as (lateral, forward) pairs, 32–35 facing change at those
482
+ // horizons (rad, + = left), 36 cyclic flag. Returns 37, or 0 without a leg chain.
483
+ animatorSlotFeatures(animatorId: u32, slot: u32, time: f32, out: Float32Array): i32
484
+ // The calibrated knee HINGE AXIS of a side (0 left / 1 right): a unit vector in the thigh's local
485
+ // frame — a skeleton property, measured over every bound clip's knee rotation track. out8 = axis
486
+ // xyz, spread mean (rad), spread max (rad), measurement count, 0, 0. 0 = no leg / no knee motion.
487
+ animatorKneeAxis(animatorId: u32, side: u32, out: Float32Array): i32
488
+ // The knee's bend plane of a slot's clip at `time` seconds (< 0 = the slot's current time),
489
+ // predicted from the hinge axis + the clip's own thigh rotation — continuous even where the leg
490
+ // is straight. out6 = pole xyz (unit, model space, toward the knee — a two-bone solver's bend
491
+ // direction), then the plane normal xyz. 0 = uncalibrated / no leg.
492
+ animatorSlotKneePole(animatorId: u32, slot: u32, side: u32, time: f32, out: Float32Array): i32
493
+ // Step warp v2: stride scales the feet's travel-direction offsets from the hips (uniform through
494
+ // stance and swing), lift = metres ADDED to their height (swing-gated by the contact marks;
495
+ // 0 = neutral, negative = a shuffle; half of what it adds raises the pelvis, capped by the
496
+ // planted legs' remaining extension), pitchDeg rotates each foot about its lateral axis
497
+ // (+ = toes up), slopeDeg the invisible staircase (+ = ascending: foot heights follow the
498
+ // incline + the feet auto-pitch; the HOST climbs the body at tan(slope) × the stride-scaled
499
+ // travel, which holds each planted foot's world height constant on its tread). Solved in the
500
+ // calibrated knee hinge plane with a soft reach; the pelvis lowers by any leg's overreach
501
+ // (marks-weighted, spring-followed, zero when nothing overreaches). 1/0/0/0 = identity;
502
+ // on false = off.
503
+ animatorSetStepWarp(animatorId: u32, on: boolean, stride: f32, lift: f32, pitchDeg: f32, slopeDeg: f32): void
504
+ // Footsteps: a contact that BEGAN this evaluation on what the base layer shows — side 0 left / 1 right,
505
+ // x/y/z = the foot's WORLD position at the plant. Fired after the frame's transform commit.
506
+ /** @custom */
507
+ setOnAnimatorStep(callback: (animatorId: number, side: number, x: number, y: number, z: number) => void): void
508
+
509
+ // ---- drives + warping ----
510
+ // The character's world velocity this frame: the speed the stride warp fits the stride to and the
511
+ // direction the orientation warp turns the lower body toward.
512
+ animatorSetMotion(animatorId: u32, vx: f32, vy: f32, vz: f32): void
513
+ // Cumulative capsule travel (m) and yaw (rad, + = left) — what a slot's distance / angle drive reads.
514
+ animatorSetDriveInput(animatorId: u32, distance: f32, angle: f32): void
515
+ // Drive a slot's clock by a quantity instead of time: 0 time / 1 distance / 2 angle. `entry` is the
516
+ // curve value that corresponds to NOW (a start 0; a stop with D metres left: total travel − D), so
517
+ // the clip is entered where it already agrees with the body. Stalls fall back to time.
518
+ animatorSetSlotDrive(animatorId: u32, slot: u32, mode: 0 | 1 | 2, entry: f32): void
519
+ // [stride on, stride min, stride max, orient on, orient max°, orient time, min speed, pelvis drop]
520
+ animatorSetWarpParams(animatorId: u32, params: Float32Array): void
521
+
522
+ // ---- feet: foot lock + ground IK (docs/animation-v2-plan.md §2.9) ----
523
+ // The feet stage runs in WORLD space inside the evaluation, after the clips are composited: a foot
524
+ // the shown clip calls planted is pinned where it landed and the leg re-solved to keep it there
525
+ // while the body moves on (the lock), and each foot is put on the ground the ENGINE probed under it,
526
+ // the pelvis lowered so the leg reaches (ground IK). The engine casts the probe rays itself against
527
+ // what a character can stand on (static + moving solids; never the character's own bodies, debris
528
+ // or sensors) and reads the CharacterController's ground state — nothing per frame from the SDK.
529
+ // [ik on, lock on, pelvis drop m, unlock distance m, lock-in s, lock-out s, pelvis spring s,
530
+ // align to ground normal 0..1, probe half-length m, detector max speed m/s, detector max height m]
531
+ // Optional: a host without it has no feet stage (the legs stay as animated).
532
+ animatorSetFeetParams?(animatorId: u32, params: Float32Array): void
533
+ // One foot after this frame's evaluation, side 0 left / 1 right: out ← [locked, lock weight,
534
+ // anchor xyz, target xyz] (world) — a debug overlay's beam under the foot. False = no such foot.
535
+ animatorFootState?(animatorId: u32, side: i32, out: Float32Array): boolean
536
+
537
+ // ---- IK chains + sockets (creator-anim anchors.cpp: the gun-master rig's hands) ----
538
+ // The chains are solved in the engine's LATE pass — after the app's update(dt) phase, before the
539
+ // dynamic bones and the skin flush — on the joints as the app left them: a world-target chain reaches
540
+ // the point / rotation given (or its target NODES, read by the engine then), an ANCHORED chain rides
541
+ // a live socket, keeping the animated end bone's pose relative to the same socket on the take's
542
+ // donor and following the playing clips' anchor spans between sockets. Nothing per frame from the
543
+ // SDK. Optional: a host without them has inert IK (the SDK warns once).
544
+ // kind 0 two-bone (the chain = the end entity, its parent, its grandparent) / 1 look-at. 0 = the
545
+ // entity is no joint of the animator.
546
+ animatorIkCreate?(animatorId: u32, kind: 0 | 1, endEntityId: u32): u32
547
+ animatorIkDestroy?(animatorId: u32, ik: u32): void
548
+ // CANIM_IK_* order: [target xyz, pole xyz, has pole, weight, rotation xyzw, rotation weight, look-at
549
+ // axis xyz, look-at limit°, enabled, anchor time s].
550
+ animatorIkSet?(animatorId: u32, ik: u32, params: Float32Array): void
551
+ // Nodes the engine reads every late pass (0 = none): the target's world position, the pole's, the
552
+ // rotation node's world rotation — in place of the fixed floats above.
553
+ animatorIkNodes?(animatorId: u32, ik: u32, targetEntity: u32, poleEntity: u32, rotationEntity: u32): void
554
+ // Anchor the chain to the live socket ('' = a world-target chain).
555
+ animatorIkAnchor?(animatorId: u32, ik: u32, socket: string): void
556
+ // out ← [reach error m, anchor delta m, anchor delta rad, applied weight]. False = no such chain.
557
+ animatorIkState?(animatorId: u32, ik: u32, out: Float32Array): boolean
558
+ // A socket: set '' = live, else a donor's name; on joint `jointEntityId` (a bone of the animator). A
559
+ // NODE socket (nodeEntityId != 0) is that entity, read every late pass (its world → the joint's
560
+ // space); else trs = [t.xyz, r.xyzw] fixed in the joint's space. No node and null trs = remove.
561
+ animatorSetSocket?(animatorId: u32, set: string, name: string, jointEntityId: u32, nodeEntityId: u32, trs: Float32Array | null): void
562
+ /** A clip's anchor spans for one bone: the donor set, one '\n'-joined socket name per span ('' = the
563
+ * joint itself), spans = (from, to, rotation 0..1, pin 0..1) per span in NORMALIZED time — FOUR floats
564
+ * a span, the engine's count is the span count. Empty spans + donor '' = remove; a clip without data
565
+ * takes no part in the anchor blend. @count count=spans/4 */
566
+ setClipAnchors?(clipSetId: u32, clip: u32, bone: string, donor: string, sockets: string, spans: Float32Array): void
567
+
568
+ // ---- locomotion: the movement model + the clip selector, both engine-side ----
569
+ // The intent (`locoSetInput`) becomes a desired velocity; the simulated velocity springs toward it
570
+ // one fixed substep at a time (`locoStep`, given the capsule's place), and `locoUpdate` — called
571
+ // before the animators are evaluated — picks and drives what the base layer shows. The host moves
572
+ // the character with the velocity and facing it reads back: in displacement `code` the animation
573
+ // never moves the body, so "how fast am I" is an input to the animation, not an output of it.
574
+ locoCreate(animatorId: u32): u32
575
+ locoDestroy(loco: u32): void
576
+ // [halflife walk, halflife run, halflife facing, speed walk, speed run, speed sprint, deadzone,
577
+ // turn min°, turn big°, blend, stop blend, start tap, resume, predict, displacement (0 code /
578
+ // 1 data / 2 hybrid), mode (0 rules / 1 matching), match interval, match blend, spin min°, turn
579
+ // rate cap (deg/s, 0 = uncapped), halflife braking, hybrid adjustment clamp (m/s)]
580
+ locoSetParams(loco: u32, params: Float32Array): void
581
+ /** @into out 64 */
582
+ locoDefaults(out: Float32Array): void
583
+ // Register a bound slot: kind 0 idle / 1 gait / 2 start / 3 stop / 4 turn / 5 match / 6 spin
584
+ // (a turn on the spot); `angle` degrees (+ = left) for the starts / turns / spins, `speed` m/s for
585
+ // a gait (0 = the clip's own measured speed), `gait` the gait a transition belongs to (0 walk /
586
+ // 1 run / 2 sprint, -1 = any) — a walking body plays the walking starts, stops and turns.
587
+ locoSetEntry(loco: u32, slot: u32, kind: i32, angle: f32, speed: f32, gait: i32): void
588
+ locoClearSet(loco: u32): void
589
+ // [dir x, dir z, magnitude 0–1, face x, face z, gait 0 walk / 1 run / 2 sprint] — the facing is
590
+ // independent of the movement: a released key with a heading still owed turns the body on the spot.
591
+ locoSetInput(loco: u32, input: Float32Array): void
592
+ // One simulation step at the capsule's world place — from the fixed substep loop.
593
+ locoStep(loco: u32, dt: f32, px: f32, py: f32, pz: f32): void
594
+ // Run the selector for this frame — before the animators are evaluated.
595
+ locoUpdate(loco: u32, dt: f32): void
596
+ // out ← [state (0 idle / 1 start / 2 move / 3 turn / 4 stop / 5 spin), speed, vel x/y/z, yaw (rad,
597
+ // 0 = +Z, + = toward +X), yaw rate, phase, state sequence, then 3 × (x, z, dir x, dir z) predicted
598
+ // at +0.2 / +0.4 / +0.7 s].
599
+ locoRead(loco: u32, out: Float32Array): void
600
+ // Why the last STOP was the one played: one row of 9 floats per candidate weighed — [slot, entry
601
+ // time, metres it still travels, metres the body needs, seconds to its next foot-down, foot-downs
602
+ // left, flags (1 = its phase matched, 2 = at/after its first foot-down, 4 = played, 8 = entered
603
+ // ahead of that foot-down by its pose), score, pose distance from what showed (m, -1 = not
604
+ // measured)]. Returns the rows written. A debug read; absent on hosts predating it.
605
+ /** @count maxRows=out/10 */
606
+ locoStopReport?(loco: u32, out: Float32Array): i32
607
+ // Build the motion-matching database over the registered set (selector mode 1); returns frames.
608
+ locoBuildDatabase(loco: u32): i32
609
+
610
+ /** Subtree AABB in the entity's OWN local space (its own transform excluded) as
611
+ * [minX,minY,minZ, maxX,maxY,maxZ] — zeros for an empty / not-yet-loaded subtree. What
612
+ * `Shape.fit()` measures. Optional: absent on hosts predating the binding. @out 6 */
613
+ computeBoundingBox?(entityId: u32): Float32Array
614
+ /** DEBUG pick: the closest TRIANGLE under a screen point (logical px) among the loaded GLB instances —
615
+ * entityId = one Model's root, 0 = every instance — CPU-skinned with the joints' CURRENT pose, both
616
+ * faces. null on a miss. One full CPU skin per call — click-rate only. Optional: a debug tool, hosts
617
+ * may lack it. */
618
+ pickTriangle?(entityId: u32, screenX: f32, screenY: f32): TrianglePick | null
619
+ setColliderFromMesh(entityId: u32, meshEntityId: u32, form: i32): void
620
+ setColliderBox(entityId: u32, centerX: f64, centerY: f64, centerZ: f64, sizeX: f64, sizeY: f64, sizeZ: f64): void
621
+ setColliderSphere(entityId: u32, centerX: f64, centerY: f64, centerZ: f64, radius: f64): void
622
+
623
+ // JoltPhysics. motionType: 0 static / 1 kinematic / 2 dynamic.
624
+ /** @c creatorHasPhysics */
625
+ physicsHasSupport(): boolean
626
+ /** Lightmap bake (engines/bake) — a dev-time tool compiled into the desktop host only; `lightmapBake` exists
627
+ * only where `lightmapHasSupport()` is true. Describes the given static instances (GLB roots / Mesh entities;
628
+ * `keysJoined` = one key per id, '
629
+ '-joined), the scene's brightest sun, its point lights (spherical emitters of
630
+ * `lightRadius` metres) and its IBL to the bake, which traces them on the NVIDIA card — direct light plus
631
+ * `bounceRays` Lambertian paths of `bounces` vertices per texel (0 / 0 = direct only) with `patchSamples` next-event
632
+ * samples over the bright patches of a first direct pass — and writes
633
+ * `<outDir>/<stem>.bake` + `<stem>-light[_n].ktx2` (irradiance in lux / lightScale, BC6H) + `<stem>-aux[_n].ktx2`
634
+ * (sun / sky visibility + light direction); `split` adds `<stem>-direct[_n]` / `-indirect[_n]` page sets for the
635
+ * SDK's two views; `denoise` runs OptiX's AI denoiser over 0 nothing, 1 the indirect part, 2 + the sky and the lamps, 3 + the sun; `filter` = the texel's reconstruction filter radius in texels (1 = ±1 texel tent, 0.5 = within the texel, 0 = the centre alone);
636
+ * `probeSpacing` > 0 places REFLECTION PROBES on a grid of that many metres and writes `<stem>-probes.ktx2` (a BC6H atlas of octahedral
637
+ * maps: one column of roughness-level tiles per probe, captured with `probeSize`² cube faces and `probeRays` paths per texel; the scene's
638
+ * IBL cubemap is what an escaping ray sees; `probeIndoor` keeps only the probes under a roof (an open share of the sphere of at most 15 %) — the
639
+ * outdoors reflects the sky through the texel's baked sky visibility instead; `volumeSpacing` (metres, 0 = none) bakes THE LIGHT GRID for movers — `<name>.lgrid`, named in the .bake's `volume`: per cell
640
+ * in free space an ambient cube without the direct sun, links between neighbours a ray connects (what `setAmbientCube`
641
+ * is fed from); `minSize` (metres, 0 = off) leaves every instance smaller than that along every axis out of the atlas — an occluder
642
+ * only, `"small": true` + its `light` (an ambient cube baked where it stands) in the .bake, applied by lightmapObjectApply; `probeLayout` 0 = ROOMS (indoor points slide to their
643
+ * room's middle and merge: one probe per room), 1 = GRID; `probeBox` = 6 floats, min xyz + max xyz, keeps the grid inside that box — null = the
644
+ * receivers' bounds) and the grid into the .bake; `debugDir` (optional) gets the float layers per page. Synchronous; progress on stdout as
645
+ * "[lightmap] …" lines, "[lightmap] done" on success. @c creatorHasLightmapBake */
646
+ lightmapHasSupport?(): boolean
647
+ /** @custom */
648
+ lightmapBake?(outDir: string, stem: string, entityIds: Uint32Array, keysJoined: string, size: u32, texel: f32, pages: u32,
649
+ sunRays: u32, skyRays: u32, lightRays: u32, lightRadius: f32, sunAngleDeg: f32, bias: f32, seed: u32,
650
+ bounceRays: u32, bounces: u32, patchSamples: u32, split: boolean, denoise: u32, filter: f32,
651
+ probeSpacing: f32, probeSize: u32, probeRays: u32, probeIndoor: boolean, probeLayout: u32, probeBox: Float32Array | null, minSize: f32, volumeSpacing: f32, emissiveNits: f32, detailTexels: u32, debugDir?: string,
652
+ transmit?: string): boolean
653
+ /** Lightmap consumption: the next createGlb takes lightmap.filamat (the material behind that instance id) for its
654
+ * OPAQUE materials and the masked twin for its MASK materials instead of the ubershader, and keeps TEXCOORD_1;
655
+ * BLEND (glass) materials always keep the ubershader. 0xFFFFFFFF clears. The same call arms the foliage tier. @custom */
656
+ setNextGlbLightmapped?(materialInstanceId: u32, maskedMaterialInstanceId?: u32): void
657
+ /** The level-wide scale every lightmap-material instance shares (present and future): lux per encoded 1.0 of the
658
+ * light atlas — the .bake's `lightScale` times whatever multiplier a debug knob wants. `emissiveNits` = the bake's
659
+ * radiance of emission 1.0 (the .bake's `emissiveNits`): over 0 the statics' glow is drawn scene-referred with it -
660
+ * a panel looks as bright as the light it gives; 0 = screen-referred. */
661
+ lightmapSetOptions?(lightScale: f32, emissiveNits?: f32): void
662
+ /** A MOVER'S SUN SHADOW ON THE BAKED STATICS: (dx, dy, dz) = TO the sun, (r, g, b) = its illuminance in lux, `on` =
663
+ * the level's aux pages end in the bake's SUN MAP (the .bake's `sunMap`; never true without it). The lightmap
664
+ * material takes the sun's part out of the baked light where the real-time shadow map says "in shadow". */
665
+ lightmapSunSet?(dx: f32, dy: f32, dz: f32, r: f32, g: f32, b: f32, on: boolean, levels?: i32): void
666
+ /** The lightmap material's DATA views, level-wide: 0 = the picture, 1 lighting only (a white diffuse material under the camera's exposure), 2 albedo, 3 geometric normal,
667
+ * 4 shading normal, 5 sun visibility, 6 sky visibility, 7 the atlas texel checker (tinted per instance), 8 light
668
+ * direction, 9 false-colour log10 lux over [p1, p2], 10 relief (E on the shading normal / E), 13 what the specular
669
+ * reflects (the probe along the reflection, or the sky + baked light without one), 14 the probe map (grey = the probes' share of the reflection, tinted by the texel's pair), 15 the sky's path of the reflection alone, 16 the probes' path alone. */
670
+ lightmapDebug?(mode: u32, p0?: f32, p1?: f32, p2?: f32, p3?: f32): void
671
+ /** The level's reflection probes, level-wide (every lightmap-material instance, present and future): the octahedral
672
+ * atlas as a loaded texture (`<stem>-probes.ktx2`, `Texture.load` like a page), its layout (`oct` = level 0's tile edge,
673
+ * `levels`, `perRow`), the probe count and the range the probe maps' records decode with (the .bake's `probes.encode`:
674
+ * lo xyz, size xyz). WHICH probes a pixel reflects is baked into the pages: every aux page carries its probe map
675
+ * under the page (per 4 x 4 block of texels the two probes those texels SEE — rays at bake time — and their weight).
676
+ * Returns the probe count; an invalid texture id clears. */
677
+ lightmapProbesSet?(atlasTextureId: u32, oct: u32, levels: u32, perRow: u32, count: u32,
678
+ lox: f32, loy: f32, loz: f32, sx: f32, sy: f32, sz: f32): u32
679
+ /** Bind the light page + the aux page + this instance's rect (uv1 * [sx, sy] + [ox, oy]) on every lightmap-material
680
+ * instance of the entity (a GLB instance's renderables, or a Mesh entity's own) and move those renderables to light
681
+ * channel 1 alone — a baked static takes nothing from the real-time sun and lamps (channel 0). Returns how many
682
+ * material instances took it (0 = not loaded lightmapped). */
683
+ lightmapApply?(entityId: u32, lightTextureId: u32, auxTextureId: u32, sx: f32, sy: f32, ox: f32, oy: f32, group?: i32): u32
684
+ /** A SMALL STATIC the atlas left out (the bake's `minSize`: `small` + `light` in the .bake): no rect — the light
685
+ * baked where it stands goes into its material parameters. `data` = 27 floats: the ambient cube — per side of its
686
+ * bounds (+x -x +y -y +z -z) the irradiance rgb in lux and the sky visibility — then probe a, probe b, a's weight
687
+ * (65535 = no probe, 65534 = the sky). The textures = any page of the level (the probes' records sit under every
688
+ * aux page). Returns the material instances that took it (0 = not loaded lightmapped). @count count=data */
689
+ lightmapObjectApply?(entityId: u32, lightTextureId: u32, auxTextureId: u32, data: Float32Array, group?: i32): u32
690
+ /** BAKED AMBIENT LIGHT for a mover, or anything on a standard or custom LIT shader: every renderable of the GLB
691
+ * instance (or the Mesh entity) takes its diffuse indirect light from `data`'s ambient cube instead of the scene's
692
+ * IBL, and the IBL's reflections through the sky visibility. `data` = 25 or 26 floats: six sides (+x -x +y -y +z -z) of
693
+ * irradiance rgb in lux + one unused float each, the sky visibility (0 a room .. 1 the open sky), then optionally the
694
+ * baked SUN visibility (1 when absent): the real-time directional light on it is multiplied by it — the baked
695
+ * statics' shadow, they cast none in real time while a light grid is loaded. Lamps stay as they are. Cheap per frame. `null` = back to the IBL. Returns the renderables touched. @c ambientCubeSet */
696
+ setAmbientCube?(entityId: u32, data: Float32Array | null): u32
697
+ /** THE LIGHT GRID for movers (`lecodes lightmap bake` writes `<name>.lgrid`, the .bake's `volume` names it):
698
+ * `lightVolumeLoad(fetchId)` hands the engine the fetched file (false = not a grid); `lightVolumeTrack(entityId, on)`
699
+ * marks a GLB instance root or a Mesh entity as a MOVER — every frame the engine gives it the ambient cube of the
700
+ * place it is at (`setAmbientCube`), blended from the reachable cells around it and eased over ~0.06 s;
701
+ * `fixed` = a STATIC the atlas left out (glass, a material the lightmap shader declines): sampled once a grid, and
702
+ * ignored when every primitive of it is baked;
703
+ * `lightVolumeSample(x, y, z)` = that cube at a point (26 floats, null = no grid or no valid cell near), for tools;
704
+ * `lightVolumeClear()` drops the grid and every mover's cube. */
705
+ lightVolumeLoad?(fetchId: bytes): boolean
706
+ lightVolumeTrack?(entityId: u32, on: boolean, fixed?: boolean): void
707
+ lightVolumeClear?(): void
708
+ /** @out 26 @out 26 */
709
+ lightVolumeSample?(x: f32, y: f32, z: f32): Float32Array | null
710
+ /** A light's channels (Filament's, 0..7). Every light is on channel 0 at creation; one that must reach BAKED statics
711
+ * too (an unbaked lamp, a muzzle flash) is added to channel 1. */
712
+ setLightChannel?(entityId: u32, channel: u32, enable: boolean): void
713
+ /** Shadow flags on every renderable of a GLB instance (Mesh has setCastShadows/setReceiveShadows). */
714
+ setGlbShadows?(entityId: u32, cast: boolean, receive: boolean): void
715
+ /** Foliage — the vegetation tier (`foliage.filamat` / `foliage-masked.filamat`, armed per model
716
+ * through setNextGlbLightmapped; the engine knows the tier by its `benders` parameter). Level-wide WIND FIELD (the
717
+ * model of Unity HDRP's Wind.hlsl, 2026-09-23): direction on the ground (normalised by the host), speed m/s,
718
+ * turbulence 0..2 = the random share of the speed, gust 0..1 = the gust field's strength, gustSpeed m/s = how fast
719
+ * the gust pattern travels. The per-material drag / stiffness / lean / shiver profile comes from the GLB's material
720
+ * extras (`lecodes.wind`) or from the plant's size. Applies to every foliage instance, present and future. */
721
+ foliageSetWind?(dirX: f32, dirZ: f32, speed: f32, turbulence: f32, gust: f32, gustSpeed: f32): void
722
+ /** Level-wide options: touchStrength = metres a bender pushes, variation 0..1 = per-copy tint, sunWrap 0..1 =
723
+ * wrapped Lambert. */
724
+ foliageSetOptions?(touchStrength: f32, variation: f32, sunWrap: f32): void
725
+ /** The leaves themselves: shiver = a multiplier on every material's shiver drag (1 = as authored, 0 = none),
726
+ * translucency 0..1 = the sun through a thin leaf lit from behind, leafShadow 0..1 = how much of a light's shadow a
727
+ * leaf card takes (bark always all of it), skyAo 0..1 = how far the baked sky visibility darkens the ambient.
728
+ * Level-wide, present and future instances. */
729
+ foliageSetLeaf?(shiver: f32, translucency: f32, leafShadow: f32, skyAo: f32): void
730
+ /** The look of the cards (2026-09-23): canopy 0..1 = the card's shading normal from its own plane to the crown's hull
731
+ * normal (hides the flat quads), normalMap = the cards' normal-map strength, edgeFade = a card seen edge-on dissolves
732
+ * over this much of |N·V|, coverage = alpha × (1 + coverage × mip level) so a distant crown keeps its density. */
733
+ foliageSetLook?(canopy: f32, normalMap: f32, edgeFade: f32, coverage: f32): void
734
+ /** An entity whose live world position bends the vegetation within `radius` metres; <= 0 removes it. The host
735
+ * writes the 8 benders nearest the camera to the shader each frame. */
736
+ foliageSetBender?(entityId: u32, radius: f32): void
737
+ /** Distance fade for every instance of the entity's ASSET (present and future): the cards thin out between `start`
738
+ * and `end` metres from the camera and past `end` the LOD pass takes the instance out of the scene. end 0 = none. */
739
+ foliageSetFade?(entityId: u32, start: f32, end: f32): void
740
+ physicsConfigure(gx: f32, gy: f32, gz: f32, maxBodies: u32): void
741
+ /** @c setPhysicsInterpolation */
742
+ setInterpolation(enabled: boolean): void
743
+ // Shape/Physics/Trigger aspects: build a shape once, create bodies from it.
744
+ physicsBuildBox(hx: f32, hy: f32, hz: f32): u32
745
+ physicsBuildSphere(radius: f32): u32
746
+ physicsBuildCylinder(halfHeight: f32, radius: f32): u32
747
+ physicsBuildCapsule(halfHeight: f32, radius: f32): u32
748
+ /** Mesh shape from node-local triangles. convex=false → triangle mesh (static/kinematic/pick/character
749
+ * only; physicsCreateBody returns 0 for a dynamic one), convex=true → convex hull (any motion).
750
+ * (sx,sy,sz) = world scale, applied natively. Returns 0 if the shape can't be built. @count vertexCount=vertices/3 */
751
+ physicsBuildMesh(vertices: Float32Array, indices: Uint32Array, convex: boolean, sx: f32, sy: f32, sz: f32): u32
752
+ /** Same from a loaded GLB root (non-skinned primitives, bind pose, baked sub-node transforms; cached per asset). */
753
+ physicsBuildMeshFromEntity(entityId: u32, convex: boolean, sx: f32, sy: f32, sz: f32): u32
754
+ /** Terrain collider (Shape { heightfield: true }, docs/terrain-plan.md §1.4): a Jolt HeightFieldShape over
755
+ * the grid `terrainCreate` draws (same arrays, same hole rule). Static / kinematic / pick / character
756
+ * ground only. (sx,sy,sz) = world scale. `physicsUpdateHeightField` rewrites a sample rectangle in
757
+ * place from the FULL arrays (live bodies keep the shape); heights beyond the range chosen at build
758
+ * time (25 % headroom) clamp — the SDK rebuilds the shape when an edit leaves that range. Optional. */
759
+ physicsBuildHeightField?(heights: Float32Array, sizeX: u32, sizeZ: u32, cellSize: f32, holes: Uint8Array | null, sx: f32, sy: f32, sz: f32): u32
760
+ physicsUpdateHeightField?(shapeId: u32, heights: Float32Array, sizeX: u32, sizeZ: u32, x0: u32, z0: u32, w: u32, h: u32, holes: Uint8Array | null): void
761
+ /** A loaded GLB root's triangle soup — the one `physicsBuildMeshFromEntity` collides — as 9 floats per
762
+ * triangle in the asset root's space (empty when the entity is not a GLB). `Terrain.conform` stamps a
763
+ * road model into the ground with it. Optional. @custom */
764
+ glbTriangles?(entityId: u32): Float32Array
765
+ /** Offset a built shape's centre from the node's origin (Shape `origin`) — world units in the
766
+ * body's rotated, UNSCALED frame, sitting outside a mesh shape's scale wrapper. Sets rather
767
+ * than accumulates; (0,0,0) clears it. Optional: an older host just centres on the node. */
768
+ physicsSetShapeOrigin?(shapeId: u32, x: f32, y: f32, z: f32): void
769
+ /** Swap a live body's shape, keeping its id, velocity and transform (Shape.fit / a re-attach).
770
+ * updateMass recomputes the inertia tensor. Refuses a triangle mesh on a dynamic body. */
771
+ physicsSetBodyShape?(bodyId: u32, shapeId: u32, updateMass: boolean): void
772
+ physicsDestroyShape(shapeId: u32): void
773
+ /** sensor = trigger (overlap events, no response); pickOnly = raycast-only, non-colliding body. */
774
+ physicsCreateBody(entityId: u32, shapeId: u32, motion: i32, mass: f32, sensor: boolean, pickOnly: boolean): u32
775
+ physicsSetPickable(bodyId: u32, pickable: boolean): void
776
+ /** Surface friction of one body (0 = ice, ~1 = grippy asphalt). Values COMBINE as sqrt(a * b), so
777
+ * a low value on either side dominates. New bodies start at 0.6 — a neutral solid surface.
778
+ * The vehicle wheel cast reads the GROUND body's value — this is what caps a car's cornering. */
779
+ physicsSetFriction(bodyId: u32, friction: f32): void
780
+ physicsGetFriction(bodyId: u32): f64
781
+ /** Ray vs pickable bodies → hit entity id (0 = miss); fills `out` = [px,py,pz,nx,ny,nz,fraction]. @into out 7 */
782
+ physicsRaycast(ox: f32, oy: f32, oz: f32, dx: f32, dy: f32, dz: f32, maxDist: f32, out?: Float32Array): u32
783
+ /** Dev-time dump of the static collision geometry (docs/navmesh-plan.md §4) — the input of
784
+ * `lecodes navmesh bake`: every STATIC solid body's triangles in world space, as an NGEO file at
785
+ * `outPath`. `entityIds`/`areas` are per-entity overrides (area 0..15, 255 unwalkable, 254 skip).
786
+ * Returns the triangle count (-1 = failed). Desktop hosts with physics only. */
787
+ physicsStaticGeometry?(outPath: string, entityIds: Uint32Array, areas: Uint8Array): i32
788
+ // CharacterController (Jolt CharacterVirtual). The character steps on the engine's FIXED clock like
789
+ // every body (frame-rate independent) and is render-interpolated; the engine owns its gravity, so
790
+ // the SDK never ticks it. Both velocity halves are LATCHED STATE, never one-shot events — JS runs
791
+ // once per frame while the sim runs 0..4 sub-steps, so a one-shot would double-apply or vanish.
792
+ // groundState: 0 OnGround / 1 OnSteepGround / 2 NotSupported / 3 InAir.
793
+ characterCreate(entityId: u32, shapeId: u32, maxSlopeDeg: f32): u32
794
+ characterDestroy(charId: u32): void
795
+ /** This frame's HORIZONTAL command (world units/s), cleared once a step consumes it — no command
796
+ * means standing still, not coasting. Held across the frame's sub-steps, and it takes the axis
797
+ * back from a latched velocity: the last writer owns X/Z. */
798
+ characterMove(charId: u32, x: f32, z: f32): void
799
+ /** The same command including the vertical — free mode (gravityScale 0): swimming / flying. */
800
+ characterMoveFree(charId: u32, x: f32, y: f32, z: f32): void
801
+ /** Seed the LATCHED ballistic vertical (jump / dash). No ground check. */
802
+ characterSetVerticalVelocity(charId: u32, vy: f32): void
803
+ /** Latch the whole velocity — it persists until a characterMove takes the axis back (knockback,
804
+ * wall jump, launch pad, weightless flight). Gravity still acts on the vertical. */
805
+ characterSetVelocity(charId: u32, x: f32, y: f32, z: f32): void
806
+ /** Multiplier over the world gravity; 0 = free mode, which ALSO disables stick-to-floor + stairs. */
807
+ characterSetGravityScale(charId: u32, scale: f32): void
808
+ characterSetMaxSlope(charId: u32, maxSlopeDeg: f32): void
809
+ /** Swap the collider live (crouch / stand up), keeping the FEET planted. Returns false when the new
810
+ * shape doesn't fit where the character stands — nothing changed, so the caller retries later and
811
+ * that retry is an exact headroom test. Optional: a host without it can't resize a character. */
812
+ characterSetShape?(charId: u32, shapeId: u32): boolean
813
+ /** The velocity the solver ENDED UP with after the last step (post-collision), not the command. @into out 3 */
814
+ characterGetVelocity(charId: u32, out: Float32Array): void
815
+ characterGetGroundState(charId: u32): i32
816
+ /** Discontinuous move (spawn / respawn / teleport) — also resets the interpolation pair. */
817
+ characterSetPosition(charId: u32, x: f32, y: f32, z: f32): void
818
+ // Vehicle (Jolt VehicleConstraint + WheeledVehicleController). One settings BLOB, so tuning knobs
819
+ // never grow this ABI — layout (floats):
820
+ // header[34]: version(15), mass, comAuto, comY,
821
+ // engTorque, engMaxRpm, engIdleRpm (0 = no floor: the engine can stall, the game
822
+ // holds idle and cranks it), engInertia, engBraking, clutchStrength,
823
+ // diffRatio (<= 0 = a fully OPEN differential),
824
+ // antiRoll (the bar's stiffness as a FRACTION of the wheel spring; 0 = no bars),
825
+ // maxTiltDeg,
826
+ // aeroDownforce, aeroDrag (each a fraction of the car's own WEIGHT at 30 m/s, scaled
827
+ // by v² from there; 0/0 = no aero, Jolt's own behaviour),
828
+ // steerMode (0 = `steer` is the wheel angle as a fraction of the lock; 1 = `steer`
829
+ // is where the driver's HANDS aim a steering column the engine integrates every
830
+ // sub-step: I·θ̈ = T_hand + T_align·(1 − assist) + T_stop − damping·θ̇ − friction,
831
+ // T_hand = clamp(handStiffness·(steer·lock − θ), ±handTorque·steerForce),
832
+ // steerForce = the input's per-frame hold — the game's policy on when the hands let go,
833
+ // T_align = the steered tires' lateral force × (pneumatic trail collapsing to
834
+ // trailFloor × trail at the curve's peak + caster) — heavy at speed, light past
835
+ // the peak, self-centring, counter-steering in a slide),
836
+ // colInertia, colDamping, colFriction (the patch's dry friction, fades out by 1.5 m/s),
837
+ // colCaster, colTrail, colTrailFloor (the aligning torque's arms — read in BOTH
838
+ // modes; steerTorque is reported either way, the force-feedback signal),
839
+ // colHandTorque, colHandStiffness, colAssist (power steering),
840
+ // colRateDeg (the HANDS' top turning speed, deg/s — they cannot push a wheel that
841
+ // outruns them, they can still hold it; the free column is uncapped; 0 = none),
842
+ // colStopDeg, colStopTorque, colStopDamping (the end stop: over the last colStopDeg
843
+ // before the lock the rack pushes back colStopTorque × depth² N·m and damps by
844
+ // colStopDamping × depth N·m·s/rad — progressive, viscous rubber; 0 band = the
845
+ // hard clamp only),
846
+ // colBearing (the column's own dry friction, N·m, at any speed — rack and bearings),
847
+ // colHandRamp (seconds the hands' torque builds to colHandTorque over; 0 = instant),
848
+ // clutchCapacity (N·m the clutch passes before it slips, × the clutch scalar; 0 = Jolt's
849
+ // viscous clutch alone),
850
+ // wheelCount, curveCount
851
+ // + curveCount * 2: the engine's normalized torque curve (x = rpm/maxRpm, y = torque/maxTorque);
852
+ // 0 points keeps Jolt's default
853
+ // + per wheel, VARIABLE length: 15 fixed floats — px, py, pz, radius, width, maxSteerDeg,
854
+ // driven (the wheel's SHARE of the engine's torque: an axle's share is the sum
855
+ // of its two, the left/right split their ratio; every wheel 0 = no drive at
856
+ // all), axle, travel, stiffness, damping,
857
+ // traction (the longitudinal impulse clamp as a multiple of friction × load;
858
+ // 1 = the physical tire, Jolt's sample runs 10), circle (friction circle 0..1:
859
+ // the share of lateral capacity the longitudinal impulse in use takes away;
860
+ // 0 = the two axes independent), sideCount, forwardCount — then
861
+ // sideCount × (slip angle °, friction) and forwardCount × (slip ratio,
862
+ // friction). Tire curves are POINTS the SDK sends (its presets are SDK-side);
863
+ // 0 points keeps Jolt's own curve.
864
+ // In steerMode 0 the steering input is RAW: `steer` × each wheel's maxSteerDeg, per fixed step —
865
+ // any taper / rate limit is the game's. In mode 1 `steer` is where the hands aim the wheel.
866
+ // NO gear list, no pedals and no wheel node ids: the gearbox is the game's (a ratio + clutch in
867
+ // the input vector), the brakes are a torque per wheel in the same vector, and the game poses its
868
+ // wheel models itself from the state. Chassis space is forward -Z / up +Y (matching node.forward);
869
+ // wheel positions are suspension attachment points in unscaled chassis space; `axle` pairs wheels
870
+ // for the differentials + anti-roll bars.
871
+ vehicleCreate(entityId: u32, shapeId: u32, settings: Float32Array): u32
872
+ vehicleDestroy(vehicleId: u32): void
873
+ /** ONE input vector, latched and applied once per fixed step: [throttle 0..1, steer −1..1 (a
874
+ * fraction of the wheels' maxSteerDeg, applied as is; with a column, where the hands aim),
875
+ * steerForce 0..1 (with a column: how firmly the hands hold the wheel — the game's per-frame
876
+ * policy on letting go; without one, ignored), ratio (the ONE gear ratio the car runs: 0 =
877
+ * neutral, negative = reverse — the engine never sees a gear list), clutch 0..1 (scales the clutch
878
+ * in the coupled engine/wheel solve), brake_0 … brake_n (N·m of brake torque per wheel, this
879
+ * step)]. A shorter vector leaves the rest as it was. Sticky; a non-zero throttle, steer or brake
880
+ * wakes a sleeping car. */
881
+ vehicleSetInput(vehicleId: u32, input: Float32Array): void
882
+ /** Re-apply the TUNABLE half of the blob (same layout) to a live car — differential, per-wheel tire
883
+ * curves / traction / circle, engine torque/RPM/curve, clutch strength + capacity, steer lock, the
884
+ * column, anti-roll stiffness, tilt limit. Structural values (mass, centre of mass, wheel geometry,
885
+ * the driven shares, suspension, whether an axle has a bar) are ignored: those need a re-create. */
886
+ vehicleSetTuning?(vehicleId: u32, settings: Float32Array): void
887
+ /** out = [speed, rpm, wheelsInContact, vx, vy, vz, wx, wy, wz, steerDeg, steerTorque] (w = chassis
888
+ * angular velocity, rad/s; steerDeg = where the road wheels are, right-positive; steerTorque = the
889
+ * tires' self-aligning torque on the steering, N·m, + = pulls right — the force-feedback signal,
890
+ * reported in both steering modes)
891
+ * + per wheel [contact, slipLong, slipAngleDeg, suspensionLength, steerDeg, spin, fLat, fLong, vLat, vSlip, load]
892
+ * (fLat/fLong = the tire's forces, N, + = to its right / pushing the car forward; vLat/vSlip = the
893
+ * patch's sliding speeds, m/s, + = to the right / tread spinning up — force × speed is heat; load =
894
+ * the suspension's force on the wheel, N). slipLong is SIGNED (+ spinning up, − locking) and so is
895
+ * slipAngleDeg (+ the patch sliding to the tire's right). suspensionLength, steerDeg and spin are
896
+ * what a wheel model's pose is built from — by the game, the SDK only exposes them. */
897
+ vehicleGetState(vehicleId: u32, out: Float32Array): void
898
+ /** Teleport upright and clear all motion (velocities, engine RPM, wheel spin); the input vector is
899
+ * zeroed — the SDK re-sends its own on the next early pass. */
900
+ vehicleReset(vehicleId: u32, x: f32, y: f32, z: f32, qx: f32, qy: f32, qz: f32, qw: f32): void
901
+ /** The chassis rigid body, for the plain body calls (physicsApplyImpulse, …). 0 if unknown. */
902
+ vehicleBodyId(vehicleId: u32): u32
903
+ // Ragdoll (Jolt Ragdoll): a Model's skeleton handed to physics — one dynamic body per listed bone
904
+ // (a capsule from the bone's origin to the next joint) joined to its parent part by a swing-twist
905
+ // constraint that carries a swing + twist MOTOR. Built ONCE from the pose the bones are in (that
906
+ // pose is the joints' neutral for the limits) and kept out of the world until ragdollActivate,
907
+ // which takes the bones' CURRENT pose and adds the bodies moving as the engine measured each bone
908
+ // over the last two frames (a swinging arm keeps swinging) plus a launch; from then on the engine
909
+ // writes the bodies' poses onto the bone entities every frame AFTER the animator, until
910
+ // ragdollDeactivate. POWERED (ragdollSetDrive): the motors pull every joint toward the relative
911
+ // rotation the ANIMATED pose shows (the engine hands the animator's pose to physics every frame,
912
+ // right after the animator wrote it and before the bodies overwrite it), and the root can be
913
+ // ANCHORED — the hips body kinematic on the animated hips — so a standing character stays in its
914
+ // animation while a hit jolts a limb and the motors bring it back; strength 0 with no anchor =
915
+ // limp. The skin joints under the hips that carry no part (the spine bones between two parts, the
916
+ // neck, the clavicles, fingers, toes) follow the animation while driven and HOLD their pose as the
917
+ // strength goes to 0 — a limp body never breathes with the loop playing underneath. Deactivating
918
+ // with a blend seeds the model's animator with the fallen pose, so whatever plays next
919
+ // transitions out of it (canimAnimatorSeedPose). Every body carries its bone entity as user
920
+ // data, so physicsRaycast / contact events name the bone. Optional: a host without it has no
921
+ // ragdolls.
922
+ // bones[partCount * 2]: bone entity, `to` entity (0 = a leaf: `length` along the parent's line;
923
+ // a leaf ROOT points along the model's up)
924
+ // settings — header[16]: version(3), partCount, stride(12), friction, linearDamping,
925
+ // angularDamping (0 = Jolt's own), collide (0 = everything, 1 = the STATIC world and
926
+ // other such ragdolls only: dynamic bodies, character controllers and vehicles pass
927
+ // through — Jolt's DEBRIS object/broadphase layer), freeze (1 = once every part of a
928
+ // LIMP body is asleep the bodies turn STATIC where they lie: the last pose keeps being
929
+ // written onto the bones, raycasts still hit, nothing wakes them, the solver skips
930
+ // them; ragdollActive reads false, ragdollDeactivate / ragdollActivate still work —
931
+ // activate makes the parts dynamic again), freezeAfter (seconds LIMP after which the
932
+ // freeze happens regardless; 0 = no cap), driveFrequency (Hz: the motors' position
933
+ // spring), driveDamping (ratio, 1 = critical), driveTorque (N·m per kg of the part:
934
+ // the motors' limit at strength 1), jointFriction (N·m per kg of the part: a torque
935
+ // that resists any joint motion, motor or not), limits (0 = the part cones below;
936
+ // 1 = LEARNED: at the first activation that finds clips bound to the model's
937
+ // animator the engine measures, per joint, how far the clips swing and twist it in
938
+ // its constraint frame and rebuilds the joints to those ranges — the motors never
939
+ // target a pose the limits forbid; no clips yet = the cones, measured at a later
940
+ // activation), limitsMargin (degrees added on each side of a learned range),
941
+ // reserved x1
942
+ // + partCount * 12: parentIndex (-1 = the root; always < the part's own index), radius, length
943
+ // (0 = up to the `to` bone), mass (kg; 0 = from the volume), swingDeg (cone
944
+ // half-angle), twistDeg (half-angle), hinge (0/1), hingeAxisX/Y/Z (in the MODEL
945
+ // node's space), hingeMinDeg, hingeMaxDeg — a hinge is a swing-twist whose cone is
946
+ // flat (2°) across the axis and [min, max] about it (a knee, an elbow)
947
+ ragdollCreate?(rootEntityId: u32, bones: Uint32Array, settings: Float32Array): u32
948
+ ragdollDestroy?(ragdollId: u32): void
949
+ /** Pose the bodies from the bones' current transforms and add them to the world, each moving as
950
+ * its bone was measured over the last two frames, plus the launch (vx, vy, vz) on every body.
951
+ * Already active = re-wake + the launch. false = unknown id / no world. */
952
+ ragdollActivate?(ragdollId: u32, vx: f32, vy: f32, vz: f32): boolean
953
+ /** Take the bodies out of the world. The bones get the bodies' last pose written against the
954
+ * parents' CURRENT worlds (move the model node under the hips first), and with `blend` > 0 the
955
+ * model's animator is seeded with it: whatever plays next transitions from the fallen pose over
956
+ * `blend` seconds. */
957
+ ragdollDeactivate?(ragdollId: u32, blend: f32): void
958
+ /** In the world AND at least one body still awake (a settled ragdoll reads false). */
959
+ ragdollActive?(ragdollId: u32): boolean
960
+ /** The rigid body of part `index` (for physicsApplyImpulseAt / velocities). 0 if unknown. */
961
+ ragdollBodyId?(ragdollId: u32, index: u32): u32
962
+ /** The joint motors' strength (0..1 = the share of driveTorque; 0 = off = limp) and the root
963
+ * anchor (the hips body kinematic on the animated hips). Kept across activations; a driven or
964
+ * anchored body never freezes. */
965
+ ragdollSetDrive?(ragdollId: u32, strength: f32, anchor: boolean): void
966
+ // Dynamic bones (the SDK's DynamicBone; creator-anim canimDyn* behind creator-gl): secondary motion
967
+ // for tails, ears, hair and cloaks — a bone tree under one root simulated as Verlet particle chains
968
+ // (gravity, wind, drag, stiffness toward the animated shape, an angle cone, capsule / sphere
969
+ // colliders, a floor plane, bone length, neighbour links) in an engine stage AFTER the late phase
970
+ // and BEFORE the skin flush: the animator, an active ragdoll and late-phase JS bone writes are the
971
+ // input, the simulated local rotations the output. Bones the animator does not drive keep their
972
+ // bind pose as the target; driven ones follow their clip. Optional: a host without it shows the
973
+ // animation alone.
974
+ // bones[boneCount]: entity ids — the root first, every other bone a child of an earlier one.
975
+ // settings — header[28]: version(1), boneCount, boneStride(7), then the chain params: weight
976
+ // (0..1 animation → simulation; 0 = off, re-arms on the animated pose), follow (0..1 of
977
+ // the root's travel carried onto the particles; 0 = full whip), wind xyz (m/s², world),
978
+ // link (0..1 neighbour-link strength), rate (substep Hz, ≤ 4 substeps per frame),
979
+ // teleport (a root jump past this many metres resets the chain), floor (0/1), floor
980
+ // point xyz, floor normal xyz (the floor slots are overridden by dynamicBoneSetFloor),
981
+ // floorFriction (a Coulomb coefficient: a resting particle's slide loses up to friction · g · dt of
982
+ // speed per substep), iterations
983
+ // (constraint passes per substep, 1..8, default 4; a long rope may want 8), side (the
984
+ // cloth's outside, one-sided colliders: 0 none, 1 away from the root bone's axis, 2 the
985
+ // next xyz in the root bone's frame), side xyz, guideHold (0..1: guides re-applied after
986
+ // every constraint pass with this fraction of their weight; 0 = once before the passes),
987
+ // edges (1 = the bone segments and links collide with the capsules too, not only the particles),
988
+ // spin (> 0: the chain in the parent bone's rotating frame + its centrifugal / Euler forces × spin;
989
+ // 0 = the translational frame, no turn forces), spinInertia (s: the cloth's own rotation follows the body's with this time constant — behind on a start, past the back on a stop; 0 = glued)
990
+ // + boneCount × 7: radius (m), stiffness (0..1 per 1/60 s toward the animated shape), damping
991
+ // (0..1 velocity lost per 1/60 s), gravity (m/s² along world −Y), angleLimit (degrees off
992
+ // the animated direction, 0 = none), mass (relative, 0 = 1: a bone-length constraint
993
+ // moves its two ends in inverse proportion; the root is kinematic; < 0 = PINNED, the bone
994
+ // rides the animation), give (m, pinned bones: a soft pin — a collider may push the bone
995
+ // this far off its animated place, its local translation is written back too; 0 = hard)
996
+ // links: (a, b) bone index pairs held at their rest distance (a cloak's columns; two linked leaves
997
+ // link their virtual tips too, so a hem stays a hem); optional.
998
+ /** @count linkCount=links/2 */
999
+ dynamicBoneCreate?(modelRootId: u32, bones: Uint32Array, settings: Float32Array, links?: Uint16Array): u32
1000
+ dynamicBoneDestroy?(id: u32): void
1001
+ /** Retune a live chain: the same blob as create (the bone rows too when the count matches). */
1002
+ dynamicBoneSet?(id: u32, settings: Float32Array): void
1003
+ /** The floor the particles stay above: mode 0 none / 1 the plane (point, normal) / 2 probe — the
1004
+ * engine casts a ray from the chain root down every frame (the ground probe, the model root
1005
+ * excluded) and uses the hit; no hit / no physics world = no floor that frame. */
1006
+ dynamicBoneSetFloor?(id: u32, mode: 0 | 1 | 2, x: f32, y: f32, z: f32, nx: f32, ny: f32, nz: f32): void
1007
+ /** The next frame snaps the chain onto the animated pose (a teleport, a cut). */
1008
+ dynamicBoneReset?(id: u32): void
1009
+ /** The colliders this chain collides with (dynamicBoneColliderCreate ids); replaces the list. */
1010
+ dynamicBoneSetColliders?(id: u32, colliderIds: Uint32Array): void
1011
+ /** The particles' world positions (bones first, then the leaves' virtual tips), 3 floats each into
1012
+ * `out`; returns the count written — a debug overlay. @count max=out/3 */
1013
+ dynamicBoneParticles?(id: u32, out: Float32Array): u32
1014
+ // guides: rows × 5 — bone index (in the chain's bone order), world x y z, weight — world points the
1015
+ // bones' particles are drawn to before the constraints; null / empty clears; refreshed every frame
1016
+ /** @count count=rows/5 */
1017
+ dynamicBoneTargets?(id: u32, rows: Float32Array | null): void
1018
+ /** A capsule a → b in the entity's local space (a == b = a sphere) that rides the entity — a bone
1019
+ * of the body the chains must not pass through. Returns a collider id (0 on failure). */
1020
+ /** `sided` 1 = one-sided: while the capsule moves away from the chain's `side` it puts a bone it has run into over to that side instead of carrying it (an arm under a cape); 0 = pushes to the nearest surface. */
1021
+ dynamicBoneColliderCreate?(entityId: u32, ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32, radius: f32, sided: f32): u32
1022
+ dynamicBoneColliderSet?(colliderId: u32, ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32, radius: f32, sided: f32): void
1023
+ dynamicBoneColliderDestroy?(colliderId: u32): void
1024
+ // Legacy coupled shape+body (still used by the worker RigidBody).
1025
+ physicsCreateBox(entityId: u32, hx: f32, hy: f32, hz: f32, motionType: i32, mass: f32): u32
1026
+ physicsCreateSphere(entityId: u32, radius: f32, motionType: i32, mass: f32): u32
1027
+ physicsCreateCylinder(entityId: u32, halfHeight: f32, radius: f32, motionType: i32, mass: f32): u32
1028
+ physicsSetLinearVelocity(bodyId: u32, x: f32, y: f32, z: f32): void
1029
+ /** @into out 3 */
1030
+ physicsGetLinearVelocity(bodyId: u32, out: Float32Array): void
1031
+ /** Angular velocity about each world axis, RADIANS/second — Jolt's unit; the SDK exposes degrees.
1032
+ * The only way to stop a spin: a position write leaves both velocities untouched. */
1033
+ physicsSetAngularVelocity?(bodyId: u32, x: f32, y: f32, z: f32): void
1034
+ /** @into out 3 */
1035
+ physicsGetAngularVelocity?(bodyId: u32, out: Float32Array): void
1036
+ physicsApplyImpulse(bodyId: u32, x: f32, y: f32, z: f32): void
1037
+ physicsApplyImpulseAt?(bodyId: u32, x: f32, y: f32, z: f32, px: f32, py: f32, pz: f32): void
1038
+ physicsSetBodyPosition(bodyId: u32, x: f32, y: f32, z: f32): void
1039
+ /** The rotation twin (normalized host-side). Both snap the body's render-interpolation pair, so a
1040
+ * discontinuous move is drawn as one rather than as a one-frame slide/spin across the gap. */
1041
+ physicsSetBodyRotation?(bodyId: u32, qx: f32, qy: f32, qz: f32, qw: f32): void
1042
+ physicsRemoveBody(bodyId: u32): void
1043
+
1044
+ /** @custom */
1045
+ createMediaPlayerTexture(id: i32): i32
1046
+
1047
+ getName(entityId: u32): string
1048
+ setName(entityId: u32, name: string): void
1049
+
1050
+ hasMesh(entityId: u32): boolean
1051
+
1052
+ /** @custom */
1053
+ createARController(sceneId: u32, cameraId: u32, mode: string, onTrack: (entityId: number, track: boolean) => void): void
1054
+ /** @custom */
1055
+ createRootAnchor(sceneId: u32): u32
1056
+ /** @custom */
1057
+ createAnchor(sceneId: u32, physicalWidth: f64, systemId: bytes): u32
1058
+
1059
+ // Particles. All emitter/curve config crosses as ONE Float32Array of [tag, payloadLen,
1060
+ // ...payload] records, parsed once in creator-particles (CPART_TAG_* in creator-particles.h; the
1061
+ // SDK mirror is in gl/Particles.ts, guarded by sdk/tests/gl/particles-tags.test.ts). Unknown
1062
+ // tags skip by length. maxParticles 0 = default (1000).
1063
+ createParticleSystem(entityId: u32, materialInstanceId: u32, maxParticles: u32): void
1064
+ spawnParticles(entityId: u32, count: f32): void
1065
+ setParticleSystemConfig(entityId: u32, data: Float32Array): void
1066
+
1067
+ // Projected decals (creator-gl src/decals.h; SDK gl/DecalSet.ts). A set on an entity = one
1068
+ // renderable of `capacity` (0 = 256) unit boxes drawn with the material instance (decal.filamat)
1069
+ // — each box projects its atlas cell onto the opaque scene behind it through the scene depth
1070
+ // buffer. A record is 27 floats in the SET entity's space: X Y Z axes scaled by the box's
1071
+ // width / height / depth (Z = out of the surface), centre, atlas rect u0 v0 u1 v1, tint rgba,
1072
+ // life (s, 0 = forever), fadeIn (s), fadeOut (s), capStart, capEnd (tilt of the image's bottom /
1073
+ // top edge in unit space — mitred trail joints, 0 = square), alphaStart, alphaEnd (opacity
1074
+ // multipliers at the bottom / top edge — a gradient along the image). addDecal returns the slot (0xFFFFFFFF = no
1075
+ // set; a full set recycles its oldest); updateDecal keeps the slot's birth time. Optional:
1076
+ // a host without them draws no decals (the SDK warns once).
1077
+ createDecalSet?(entityId: u32, materialInstanceId: u32, capacity: u32): void
1078
+ addDecal?(entityId: u32, record: Float32Array): u32
1079
+ updateDecal?(entityId: u32, slot: u32, record: Float32Array): void
1080
+ removeDecal?(entityId: u32, slot: u32): void
1081
+ clearDecals?(entityId: u32): void
1082
+ decalCount?(entityId: u32): u32
1083
+
1084
+ createNoise(): u32
1085
+ setNoiseFrequency(noiseId: u32, frequency: f32): void
1086
+ setNoiseOctaves(noiseId: u32, octaves: f32): void
1087
+ setNoiseFractalLunacrity(noiseId: u32, lunacrity: f32): void
1088
+ setNoiseFractalGain(noiseId: u32, fractalGain: f32): void
1089
+ getNoise2D(noiseId: u32, x: f32, y: f32): f64
1090
+ getNoise3D(noiseId: u32, x: f32, y: f32, z: f32): f64
1091
+
1092
+ /** @custom */
1093
+ captureImage(sceneId: number, onComplete: (bufferId: number, name: string, size: number) => void, onReject: () => void): void
1094
+ }
1095
+ }
1096
+
1097
+ /** The 3D HOST's side of the contract — what the engine needs from the platform around Filament
1098
+ * (`HostGL` of creator-pkg/host.gen.h): the GL context to share, the scene view's lifecycle, image
1099
+ * decoding into textures, AR / VR sessions. OPTIONAL as a whole (a UI-only build has no 3D); every
1100
+ * slot but `createTexture` is optional — the runtime skips what a host does not fill.
1101
+ * @host gl HostGL @optional */
1102
+ export interface HostGL {
1103
+ /** The host's GL context Filament shares with (a desktop master context, the XR context on Quest);
1104
+ * absent = the engine creates its own. */
1105
+ getGLContext?(): handle
1106
+ /** Fired on the JS thread right after createEngine built (or rebuilt) the engine, success or not — a
1107
+ * host that released its own context for the share re-binds it here. */
1108
+ engineCreated?(): void
1109
+ /** The legacy first scene.open(): create the scene view / swap chain lazily. */
1110
+ createGLView?(): void
1111
+ /** The scene closed for good: drop the scene view, stop compositing. */
1112
+ closeScene?(): void
1113
+ /** Decode host buffer `systemId` (PNG / JPEG / KTX2) into an engine texture (`flags` = Texture.load's
1114
+ * options bitmask, 1 = linear data); settle `onComplete(textureId, width, height)` or `onReject`
1115
+ * through the dispatch bridge. */
1116
+ createTexture(systemId: bytes, flags: u32, onComplete: (textureId: u32, width: i32, height: i32) => void, onReject: (err: string) => void): void
1117
+ /** Import a media player's frames as an engine texture (`_creator.createMediaPlayerTexture`); 0 = none. */
1118
+ createMediaPlayerTexture?(playerId: i32): i32
1119
+ /** Start an AR session for the scene; settle one callback through the dispatch bridge. */
1120
+ launchAR?(sceneId: u32, onComplete: () => void, onReject: (err: string) => void): void
1121
+ stopAR?(): void
1122
+ /** Start a VR (OpenXR) session for the scene; absent = "VR is not supported on this device". */
1123
+ launchVR?(sceneId: u32, onComplete: () => void, onReject: (err: string) => void): void
1124
+ stopVR?(): void
1125
+ /** Bind the AR camera of `sceneId` to `cameraId` in `mode` ("world" / "face" / …). */
1126
+ createARController?(sceneId: u32, cameraId: u32, mode: string): void
1127
+ /** The AR root anchor entity of a scene. */
1128
+ createRootAnchor?(sceneId: u32, entityId: u32): void
1129
+ /** An image anchor: track the image in host buffer `systemId` (`physicalWidth` in metres) on `entityId`. */
1130
+ createAnchor?(sceneId: u32, entityId: u32, physicalWidth: f32, systemId: bytes): void
1131
+ }
1132
+
1133
+ export {}