lecodes-cli 0.18.2 → 0.19.2

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 (224) hide show
  1. package/dist/index.js +1866 -301
  2. package/package.json +14 -12
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/core/Aspect.ts +0 -512
  86. package/runtime/sdk/core/InspectorUI.ts +0 -212
  87. package/runtime/sdk/core/color.ts +0 -66
  88. package/runtime/sdk/core/compWrite.ts +0 -42
  89. package/runtime/sdk/core/events.ts +0 -38
  90. package/runtime/sdk/core/fields.ts +0 -120
  91. package/runtime/sdk/core/registry.ts +0 -23
  92. package/runtime/sdk/core/signals.ts +0 -277
  93. package/runtime/sdk/core/time.ts +0 -81
  94. package/runtime/sdk/g2/Camera2D.ts +0 -40
  95. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  96. package/runtime/sdk/g2/Node2D.ts +0 -267
  97. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  98. package/runtime/sdk/g2/Physics2D.ts +0 -346
  99. package/runtime/sdk/g2/Scene2D.ts +0 -209
  100. package/runtime/sdk/g2/Shape2D.ts +0 -259
  101. package/runtime/sdk/g2/Sprite.ts +0 -89
  102. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  103. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  104. package/runtime/sdk/g2/Texture2D.ts +0 -47
  105. package/runtime/sdk/g2/Tilemap.ts +0 -41
  106. package/runtime/sdk/g2/Tileset.ts +0 -71
  107. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  108. package/runtime/sdk/g2/autotile.ts +0 -433
  109. package/runtime/sdk/g2/cells.ts +0 -91
  110. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  111. package/runtime/sdk/g2/groups2d.ts +0 -106
  112. package/runtime/sdk/g2/loop.ts +0 -50
  113. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  114. package/runtime/sdk/g2/touch.ts +0 -83
  115. package/runtime/sdk/gl/Camera.ts +0 -160
  116. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  117. package/runtime/sdk/gl/CharacterController.ts +0 -238
  118. package/runtime/sdk/gl/Gearbox.ts +0 -212
  119. package/runtime/sdk/gl/Geometry.ts +0 -279
  120. package/runtime/sdk/gl/IK.ts +0 -193
  121. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  122. package/runtime/sdk/gl/Light.ts +0 -99
  123. package/runtime/sdk/gl/Lightmap.ts +0 -179
  124. package/runtime/sdk/gl/Material.ts +0 -245
  125. package/runtime/sdk/gl/Mesh.ts +0 -83
  126. package/runtime/sdk/gl/Model.ts +0 -64
  127. package/runtime/sdk/gl/Node.ts +0 -350
  128. package/runtime/sdk/gl/Noise.ts +0 -30
  129. package/runtime/sdk/gl/Particles.ts +0 -676
  130. package/runtime/sdk/gl/Physics.ts +0 -222
  131. package/runtime/sdk/gl/Plane.ts +0 -53
  132. package/runtime/sdk/gl/Ray.ts +0 -16
  133. package/runtime/sdk/gl/Scene.ts +0 -479
  134. package/runtime/sdk/gl/Shape.ts +0 -377
  135. package/runtime/sdk/gl/Texture.ts +0 -46
  136. package/runtime/sdk/gl/Trigger.ts +0 -45
  137. package/runtime/sdk/gl/Vehicle.ts +0 -473
  138. package/runtime/sdk/gl/Wheel.ts +0 -240
  139. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  140. package/runtime/sdk/gl/animation/Animator.ts +0 -87
  141. package/runtime/sdk/gl/animation/Layer.ts +0 -29
  142. package/runtime/sdk/gl/animation/Loop.ts +0 -25
  143. package/runtime/sdk/gl/animation/Playback.ts +0 -43
  144. package/runtime/sdk/gl/animation/core.ts +0 -294
  145. package/runtime/sdk/gl/controls.ts +0 -95
  146. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  147. package/runtime/sdk/gl/scenarios.ts +0 -291
  148. package/runtime/sdk/gl/state.ts +0 -6
  149. package/runtime/sdk/gl/touch.ts +0 -68
  150. package/runtime/sdk/inject.ts +0 -186
  151. package/runtime/sdk/math/Mathf.ts +0 -118
  152. package/runtime/sdk/math/mat4.ts +0 -278
  153. package/runtime/sdk/math/quat.ts +0 -232
  154. package/runtime/sdk/math/vec.ts +0 -255
  155. package/runtime/sdk/plugins/camera.ts +0 -81
  156. package/runtime/sdk/plugins/geolocation.ts +0 -123
  157. package/runtime/sdk/plugins/oauth.ts +0 -61
  158. package/runtime/sdk/plugins/permission.ts +0 -7
  159. package/runtime/sdk/plugins/push.ts +0 -132
  160. package/runtime/sdk/plugins/qr.ts +0 -73
  161. package/runtime/sdk/plugins/service.ts +0 -47
  162. package/runtime/sdk/runtime/app.ts +0 -101
  163. package/runtime/sdk/runtime/appEvents.ts +0 -54
  164. package/runtime/sdk/runtime/channel.ts +0 -61
  165. package/runtime/sdk/runtime/clipboard.ts +0 -20
  166. package/runtime/sdk/runtime/datetime.ts +0 -329
  167. package/runtime/sdk/runtime/device.ts +0 -293
  168. package/runtime/sdk/runtime/fetch.ts +0 -77
  169. package/runtime/sdk/runtime/files.ts +0 -23
  170. package/runtime/sdk/runtime/input.ts +0 -175
  171. package/runtime/sdk/runtime/media.ts +0 -111
  172. package/runtime/sdk/runtime/misc.ts +0 -16
  173. package/runtime/sdk/runtime/net.ts +0 -36
  174. package/runtime/sdk/runtime/rpc.ts +0 -218
  175. package/runtime/sdk/runtime/service.ts +0 -83
  176. package/runtime/sdk/runtime/share.ts +0 -9
  177. package/runtime/sdk/runtime/storage.ts +0 -13
  178. package/runtime/sdk/runtime/touch.ts +0 -76
  179. package/runtime/sdk/scene/defineScene.ts +0 -1227
  180. package/runtime/sdk/scene/editorPlugins.ts +0 -92
  181. package/runtime/sdk/scene/gizmos.ts +0 -148
  182. package/runtime/sdk/scene/grammar.ts +0 -120
  183. package/runtime/sdk/scene/material.ts +0 -188
  184. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  185. package/runtime/sdk/server/auth/global.ts +0 -80
  186. package/runtime/sdk/server/auth/host.ts +0 -318
  187. package/runtime/sdk/server/auth/models.ts +0 -83
  188. package/runtime/sdk/server/auth/types.ts +0 -50
  189. package/runtime/sdk/server/channel.ts +0 -56
  190. package/runtime/sdk/server/context.ts +0 -36
  191. package/runtime/sdk/server/db/defineDb.ts +0 -237
  192. package/runtime/sdk/server/db/fields.ts +0 -132
  193. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  194. package/runtime/sdk/server/db/index.ts +0 -7
  195. package/runtime/sdk/server/db/marci/query.ts +0 -412
  196. package/runtime/sdk/server/db/types.ts +0 -202
  197. package/runtime/sdk/server/errors.ts +0 -12
  198. package/runtime/sdk/server/host.ts +0 -74
  199. package/runtime/sdk/server/inject.ts +0 -13
  200. package/runtime/sdk/server/runtime.ts +0 -133
  201. package/runtime/sdk/server/validate.ts +0 -87
  202. package/runtime/sdk/ui/NativeView.ts +0 -142
  203. package/runtime/sdk/ui/UI.ts +0 -39
  204. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  205. package/runtime/sdk/ui/UIButton.ts +0 -101
  206. package/runtime/sdk/ui/UIContainer.ts +0 -60
  207. package/runtime/sdk/ui/UIImage.ts +0 -83
  208. package/runtime/sdk/ui/UIInput.ts +0 -185
  209. package/runtime/sdk/ui/UIModal.ts +0 -139
  210. package/runtime/sdk/ui/UINode.ts +0 -826
  211. package/runtime/sdk/ui/UIPager.ts +0 -362
  212. package/runtime/sdk/ui/UIPopover.ts +0 -100
  213. package/runtime/sdk/ui/UIScreen.ts +0 -123
  214. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  215. package/runtime/sdk/ui/UISpacer.ts +0 -14
  216. package/runtime/sdk/ui/UITabs.ts +0 -236
  217. package/runtime/sdk/ui/UIText.ts +0 -51
  218. package/runtime/sdk/ui/UIVideo.ts +0 -88
  219. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  220. package/runtime/sdk/ui/UIWidget.ts +0 -127
  221. package/runtime/sdk/ui/fonts.ts +0 -13
  222. package/runtime/sdk/ui/presentable.ts +0 -117
  223. package/runtime/sdk/ui/router.ts +0 -132
  224. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,77 +0,0 @@
1
- // HTTP via the host. `fetch` is async (host streams the body), `fetchLocal` reads a preloaded asset
2
- // synchronously. Ported from worker/src/fetch.ts over the same _creatorUtils bridge.
3
-
4
- export type FetchOptions = {
5
- body?: any
6
- method?: string
7
- headers?: Record<string, string>
8
- useOnce?: boolean
9
- onProgress?: (progress: { loaded: number, total?: number }) => void
10
- }
11
-
12
- export interface FetchResponse {
13
- readonly status: number
14
- json<T = any>(): T
15
- text(): string
16
- dispose(): void
17
- }
18
-
19
- class FetchResponseImpl implements FetchResponse {
20
- readonly status: number
21
- private readonly _id: number
22
- constructor(id: number, status: number) {
23
- this._id = id
24
- this.status = status
25
- }
26
- json<T = any>(): T {
27
- return _creatorUtils.fetchToJson(this._id)
28
- }
29
- text(): string {
30
- return _creatorUtils.fetchToText(this._id)
31
- }
32
- dispose(): void {
33
- _creatorUtils.disposeFetch(this._id)
34
- }
35
- }
36
-
37
- export const fetch = (url: string, options: FetchOptions = {}): Promise<FetchResponse> =>
38
- new Promise<FetchResponse>((resolve, reject) => {
39
- _creatorUtils.fetch(url, options, (systemId, statusCode) => {
40
- resolve(new FetchResponseImpl(systemId, statusCode))
41
- }, reject)
42
- })
43
-
44
- export const fetchLocal = (path: string): FetchResponse =>
45
- new FetchResponseImpl(_creatorUtils.fetchLocal(path), 200)
46
-
47
- /** A handle to a picked / fetched file (see openFilePicker). */
48
- export class File {
49
- /** @internal host buffer handle. `_`-prefixed because hosts read it off the raw object —
50
- * same key `FetchResponse` carries, so both are accepted anywhere a buffer is expected
51
- * (FormData entries, `UIImage` src, `Texture.load`, `share`). */
52
- readonly _id: number
53
- readonly name: string
54
- readonly size: number
55
- constructor(systemId: number, name: string, size: number) {
56
- this._id = systemId
57
- this.name = name
58
- this.size = size
59
- }
60
- }
61
-
62
- type FormDataValue = string | number | boolean | File | FetchResponse
63
-
64
- export class FormData {
65
- // Host-facing: hosts walk `_entries` and treat any object value carrying `_id` as binary
66
- // (the buffer id), everything else as a text field. See viewer-lite utils/fetch.ts,
67
- // creator-pkg creatorUtilsHandler.cpp, iOS Fetch.swift buildMultipartBody.
68
- private _entries: [string, FormDataValue, filename?: string][] = []
69
-
70
- append(name: string, value: FormDataValue, filename?: string): void {
71
- this._entries.push([ name, value, filename ?? (typeof value === "object" ? (value as any).name : undefined) ])
72
- }
73
- delete(name: string): void {
74
- const index = this._entries.findIndex((i) => i[0] === name)
75
- if (index >= 0) this._entries.splice(index, 1)
76
- }
77
- }
@@ -1,23 +0,0 @@
1
- // Native file picker. Ported from worker/src/utils/openFilePicker.ts; resolves to File handle(s)
2
- // (see runtime/fetch.ts) that can be appended to a FormData and uploaded.
3
-
4
- import { File } from "./fetch"
5
-
6
- type OpenFilePickerOptions = { accept?: string }
7
-
8
- export function openFilePicker(options?: OpenFilePickerOptions & { multiple?: false }): Promise<File | null>
9
- export function openFilePicker(options: OpenFilePickerOptions & { multiple: true }): Promise<File[]>
10
- export function openFilePicker(
11
- options: OpenFilePickerOptions & { multiple?: boolean } = {},
12
- ): Promise<File | File[] | null> {
13
- return new Promise((resolve, reject) => {
14
- _creatorUtils.openFilePicker((resp) => {
15
- if (!resp || resp.length === 0) {
16
- resolve(options.multiple ? [] : null)
17
- return
18
- }
19
- if (options.multiple) resolve(resp.map((i: any) => new File(i[0], i[1], i[2])))
20
- else resolve(new File(resp[0][0], resp[0][1], resp[0][2]))
21
- }, reject, !!options.multiple, options.accept)
22
- })
23
- }
@@ -1,175 +0,0 @@
1
- // Keyboard / mouse / gamepad input behind one `Input` global (docs/input-plan.md).
2
- //
3
- // Two kinds of signal, two shapes of API:
4
- // - BUTTONS — anything that goes down and up: keyboard keys, mouse buttons, gamepad buttons. One
5
- // code vocabulary ('KeyW', 'MouseLeft', 'GamepadSouth'), one held-poll (`Input.key`) and one
6
- // event pair (`Input.on('keydown' | 'keyup')`). A mouse button IS a key.
7
- // - CONTINUOUS — mouse motion/wheel, sticks, triggers: polled every frame (`Input.mouse.delta`,
8
- // `Input.gamepad(0).axis('leftX')`), never events.
9
- // Bindings ("jump = Space | GamepadSouth") are app code: compare `e.code`, OR a few `Input.key`s.
10
- // Hosts report physical state only (`_creatorUtils.inputKey/inputRead/registerInputEvent`).
11
-
12
- /** Continuous channel ids — the `_creatorUtils.inputRead` contract (bridges.d.ts). */
13
- export const InputChannel = {
14
- MouseX: 0, MouseY: 1, MouseDX: 2, MouseDY: 3, WheelX: 4, WheelY: 5, PointerLocked: 6,
15
- GamepadCount: 7,
16
- GamepadBase: 16, GamepadStride: 16,
17
- PadConnected: 0, PadLeftX: 1, PadLeftY: 2, PadRightX: 3, PadRightY: 4, PadLeftTrigger: 5, PadRightTrigger: 6,
18
- } as const
19
-
20
- export type InputEventName = "keydown" | "keyup" | "gamepadconnected" | "gamepaddisconnected"
21
-
22
- /** `keydown` / `keyup` payload. `code` is the same string `Input.key()` takes. */
23
- export interface InputKeyEvent {
24
- readonly type: "keydown" | "keyup"
25
- /** Button code: KeyboardEvent.code ('KeyW', 'Space'), 'MouseLeft|Right|Middle|Back|Forward', or
26
- * 'GamepadSouth|East|West|North|L1|R1|L2|R2|Select|Start|L3|R3|Up|Down|Left|Right'. */
27
- readonly code: string
28
- /** Which device: 'keyboard' | 'mouse' | 'gamepad'. */
29
- readonly source: "keyboard" | "mouse" | "gamepad"
30
- /** Pad index (0..3) for gamepad buttons, undefined otherwise. */
31
- readonly gamepad?: number
32
- /** OS auto-repeat keydown (key still held) — ignore for one-shot actions. */
33
- readonly repeat: boolean
34
- }
35
-
36
- export interface InputGamepadEvent {
37
- readonly type: "gamepadconnected" | "gamepaddisconnected"
38
- readonly gamepad: number
39
- }
40
-
41
- type Listener<E> = (e: E) => void
42
- type InputEventMap = {
43
- keydown: InputKeyEvent
44
- keyup: InputKeyEvent
45
- gamepadconnected: InputGamepadEvent
46
- gamepaddisconnected: InputGamepadEvent
47
- }
48
-
49
- const listeners: { [K in InputEventName]?: Listener<InputEventMap[K]>[] } = {}
50
- let hostListenerInstalled = false
51
-
52
- const sourceOf = (code: string): InputKeyEvent["source"] =>
53
- code.startsWith("Mouse") ? "mouse" : code.startsWith("Gamepad") ? "gamepad" : "keyboard"
54
-
55
- const emit = <K extends InputEventName>(name: K, e: InputEventMap[K]) => {
56
- const list = listeners[name]
57
- if (!list || list.length === 0) return
58
- for (const fn of [...list]) fn(e as any)
59
- }
60
-
61
- // One fan-out for the host channel and the test/host `_emit` seam.
62
- const deliver = (kind: number, code: string, gamepad: number, repeat: number) => {
63
- if (kind === 0 || kind === 1) {
64
- const src = sourceOf(code)
65
- emit(kind === 0 ? "keydown" : "keyup", {
66
- type: kind === 0 ? "keydown" : "keyup", code, source: src,
67
- gamepad: src === "gamepad" && gamepad >= 0 ? gamepad : undefined, repeat: repeat !== 0,
68
- })
69
- } else if (kind === 2 || kind === 3) {
70
- emit(kind === 2 ? "gamepadconnected" : "gamepaddisconnected",
71
- { type: kind === 2 ? "gamepadconnected" : "gamepaddisconnected", gamepad })
72
- }
73
- }
74
-
75
- // Lazily claim the host's single listener slot (never at module scope — chisel drops top-level
76
- // side effects, and a host without the channel simply never fires).
77
- const ensureHostListener = () => {
78
- if (hostListenerInstalled) return
79
- hostListenerInstalled = true
80
- _creatorUtils.registerInputEvent?.(deliver)
81
- }
82
-
83
- const read = (channel: number): number => _creatorUtils.inputRead?.(channel) ?? 0
84
-
85
- /** Mouse state — polled. Position is the cursor inside the viewport in logical px; `delta` is the
86
- * motion during the previous frame (raw where the OS offers it) and keeps counting while locked —
87
- * that's FPS look. Hosts without a mouse report zeros. */
88
- export const mouse = {
89
- get position(): { x: number, y: number } { return { x: read(InputChannel.MouseX), y: read(InputChannel.MouseY) } },
90
- get delta(): { x: number, y: number } { return { x: read(InputChannel.MouseDX), y: read(InputChannel.MouseDY) } },
91
- /** Wheel notches during the previous frame (+y = wheel toward you / scroll down). */
92
- get wheel(): { x: number, y: number } { return { x: read(InputChannel.WheelX), y: read(InputChannel.WheelY) } },
93
- /** Pointer lock: cursor hidden + confined, `delta` keeps flowing. Web grants it only from a user
94
- * gesture (call it in a 'keydown' handler for MouseLeft); hosts drop it on focus loss and
95
- * re-acquire on focus. Escape does NOT unlock by itself on native hosts — call `unlock()`. */
96
- get locked(): boolean { return read(InputChannel.PointerLocked) !== 0 },
97
- lock(): boolean { return _creatorUtils.inputSetPointerLock?.(true) ?? false },
98
- unlock(): void { _creatorUtils.inputSetPointerLock?.(false) },
99
- }
100
-
101
- export type GamepadAxisName = "leftX" | "leftY" | "rightX" | "rightY" | "leftTrigger" | "rightTrigger"
102
- const PAD_AXIS: Record<GamepadAxisName, number> = {
103
- leftX: InputChannel.PadLeftX, leftY: InputChannel.PadLeftY, rightX: InputChannel.PadRightX,
104
- rightY: InputChannel.PadRightY, leftTrigger: InputChannel.PadLeftTrigger, rightTrigger: InputChannel.PadRightTrigger,
105
- }
106
-
107
- export interface GamepadState {
108
- readonly index: number
109
- readonly connected: boolean
110
- /** Stick axes −1..1 (+Y = down, like the web Gamepad API), triggers 0..1. `deadzone` (default
111
- * 0.15) zeroes small stick drift — pass 0 for the raw value. */
112
- axis(name: GamepadAxisName, deadzone?: number): number
113
- /** Held? Same as `Input.key(code, index)`. */
114
- button(code: string): boolean
115
- }
116
-
117
- const padCache: GamepadState[] = []
118
- const gamepadAt = (index: number): GamepadState => {
119
- let g = padCache[index]
120
- if (!g) {
121
- const base = InputChannel.GamepadBase + InputChannel.GamepadStride * index
122
- g = padCache[index] = {
123
- index,
124
- get connected() { return read(base + InputChannel.PadConnected) !== 0 },
125
- axis(name, deadzone = 0.15) {
126
- const v = read(base + PAD_AXIS[name])
127
- return deadzone > 0 && Math.abs(v) < deadzone ? 0 : v
128
- },
129
- button(code) { return _creatorUtils.inputKey(code, index) },
130
- }
131
- }
132
- return g
133
- }
134
-
135
- export const Input = {
136
- /**
137
- * Is a button held? `code` is the physical key (`KeyboardEvent.code`: 'ArrowRight', 'KeyW',
138
- * 'Space'), a mouse button ('MouseLeft', 'MouseRight', 'MouseMiddle') or a gamepad button
139
- * ('GamepadSouth' = A/Cross, 'GamepadR2' = right trigger as a button …). Poll inside setLoop for
140
- * frame-independent movement. `gamepad` scopes a Gamepad* code to one pad; omitted = any pad.
141
- */
142
- key(code: string, gamepad?: number): boolean {
143
- return _creatorUtils.inputKey(code, gamepad)
144
- },
145
-
146
- /** Listen for a discrete input moment. `keydown`/`keyup` cover EVERY button — keyboard, mouse,
147
- * gamepad — filter on `e.code`. Use these for one-shot actions (jump, shoot, charge-release);
148
- * `Input.key` for continuous ones (walk). */
149
- on<K extends InputEventName>(name: K, listener: Listener<InputEventMap[K]>): void {
150
- ensureHostListener()
151
- const list = (listeners[name] ??= []) as Listener<InputEventMap[K]>[]
152
- if (!list.includes(listener)) list.push(listener)
153
- },
154
- off<K extends InputEventName>(name: K, listener: Listener<InputEventMap[K]>): void {
155
- const list = listeners[name] as Listener<InputEventMap[K]>[] | undefined
156
- if (!list) return
157
- const i = list.indexOf(listener)
158
- if (i >= 0) list.splice(i, 1)
159
- },
160
-
161
- mouse,
162
-
163
- /** Gamepad `index` (0..3). Always returns an object — check `.connected`. */
164
- gamepad(index = 0): GamepadState { return gamepadAt(index) },
165
- /** Indices of the currently connected gamepads. */
166
- gamepads(): number[] {
167
- const out: number[] = []
168
- const n = read(InputChannel.GamepadCount)
169
- for (let i = 0; i < 4 && out.length < n; i++) if (gamepadAt(i).connected) out.push(i)
170
- return out
171
- },
172
-
173
- /** @internal — hosts/tests: deliver an event exactly as the host listener would. */
174
- _emit(kind: number, code: string, gamepad: number, repeat: number): void { deliver(kind, code, gamepad, repeat) },
175
- }
@@ -1,111 +0,0 @@
1
- // Audio / video playback over the host. Ported from worker/src/MediaPlayer.ts, with the listener
2
- // boilerplate folded into the shared Emitter. VideoPlayer.texture (a 3D Texture) is wired in Phase 4
3
- // when the Texture type exists — see gl/.
4
- //
5
- // The host no longer routes end-of-playback events back by id through a Registry: each player hands
6
- // the host a `_finished` callback at creation time. The host holds the JS closure directly — valid
7
- // on every backend (web JS, Android quickjs, iOS JavaScriptCore) — and invokes it when a play-through
8
- // completes, so there is no id table to keep in sync.
9
-
10
- import { Emitter } from "../core/events"
11
- import { Texture } from "../gl/Texture"
12
-
13
- type MediaEvents = {
14
- /** Fired at each loop boundary while `loop` is true. */
15
- loopReached: () => void
16
- /** Fired once when playback reaches the end while `loop` is false. */
17
- completed: () => void
18
- }
19
-
20
- abstract class MediaPlayer extends Emitter<MediaEvents> {
21
- protected _id = -1
22
- protected _src = ""
23
- private _playing = false
24
- private _loop = false
25
- private _volume = 1
26
- private _disposed = false
27
-
28
- // Every host-touching operation runs through this so a disposed player can never address a freed
29
- // (and possibly reused) host slot.
30
- private _assertAlive(): void {
31
- if (this._disposed) throw new Error("MediaPlayer has been disposed")
32
- }
33
-
34
- play(): void { this.playing = true }
35
- pause(): void { this.playing = false }
36
-
37
- get playing(): boolean { return this._playing }
38
- set playing(value: boolean) {
39
- this._assertAlive()
40
- this._playing = value
41
- _creatorUtils.updateMediaPlayerPlaying(this._id, value)
42
- }
43
-
44
- get volume(): number { return this._volume }
45
- set volume(value: number) {
46
- this._assertAlive()
47
- this._volume = value
48
- _creatorUtils.updateMediaPlayerVolume(this._id, value)
49
- }
50
-
51
- get loop(): boolean { return this._loop }
52
- set loop(value: boolean) {
53
- this._assertAlive()
54
- this._loop = value
55
- _creatorUtils.updateMediaPlayerLoop(this._id, value)
56
- }
57
-
58
- /** Current playback position, in seconds. Settable to seek. */
59
- get time(): number { this._assertAlive(); return _creatorUtils.getMediaPlayerTime(this._id) }
60
- set time(value: number) { this._assertAlive(); _creatorUtils.setMediaPlayerTime(this._id, value) }
61
-
62
- /** Total media length in seconds (0 until metadata has loaded). */
63
- get duration(): number { this._assertAlive(); return _creatorUtils.getMediaPlayerDuration(this._id) }
64
-
65
- /** True once {@link dispose} has been called; the player is unusable afterwards. */
66
- get disposed(): boolean { return this._disposed }
67
-
68
- /**
69
- * Release the underlying host media resource (decoder, network, buffers) immediately and detach
70
- * all listeners. Idempotent. Any further use of the player throws.
71
- */
72
- dispose(): void {
73
- if (this._disposed) return
74
- this._disposed = true
75
- this._playing = false
76
- this.clearListeners()
77
- _creatorUtils.removeMediaPlayer(this._id)
78
- }
79
-
80
- /** @internal — the host calls this when a play-through finishes. */
81
- protected _finished(): void {
82
- if (this._disposed) return
83
- if (this._loop) {
84
- this.dispatch("loopReached")
85
- } else {
86
- this._playing = false
87
- this.dispatch("completed")
88
- }
89
- }
90
- }
91
-
92
- export class AudioPlayer extends MediaPlayer {
93
- constructor(src?: string) {
94
- super()
95
- this._src = src ?? ""
96
- this._id = _creatorUtils.createAudioPlayer(this._src, () => this._finished())
97
- }
98
- }
99
-
100
- export class VideoPlayer extends MediaPlayer {
101
- constructor(src?: string) {
102
- super()
103
- this._src = src ?? ""
104
- this._id = _creatorUtils.createVideoPlayer(this._src, () => this._finished())
105
- }
106
-
107
- /** A 3D Texture that samples this video (e.g. for a Material.video()). */
108
- get texture(): Texture {
109
- return new Texture(0, 0, _creator.createMediaPlayerTexture(this._id))
110
- }
111
- }
@@ -1,16 +0,0 @@
1
- // Small platform globals that are real (not engine-specific). Grown in Phase 1.
2
-
3
- /** Show a transient toast notification. */
4
- export const toast = (msg: string): void => _creatorUI.toast(msg)
5
-
6
- /** Open `url` in the system browser / external handler (fire-and-forget, like `share`). Silent
7
- * no-op on hosts without one (headless). */
8
- export const openURL = (url: string): void => _creatorUtils.openUrl?.(url)
9
-
10
- export type SvgSourceValue = {
11
- readonly svg: string
12
- tintColor: string | null
13
- }
14
-
15
- /** Wrap raw SVG XML so it can be used as an image source. */
16
- export const SvgSource = (svg: string): SvgSourceValue => ({ svg, tintColor: null })
@@ -1,36 +0,0 @@
1
- // WebSocket over the host bridge. Same event surface as the browser's (open/message/close/error)
2
- // via the shared Emitter. Ported from worker/src/WebSocket.ts.
3
-
4
- import { Emitter } from "../core/events"
5
-
6
- type SocketEvents = {
7
- open: () => void
8
- message: (data: any) => void
9
- close: (code?: number) => void
10
- error: () => void
11
- }
12
-
13
- export class WebSocket extends Emitter<SocketEvents> {
14
- private _id: number | null = null
15
-
16
- constructor(url: string, headers?: Record<string, string>) {
17
- super()
18
- this._id = _creatorUtils.websocketOpen(url, (channel, data) => this._onMessage(channel, data), headers)
19
- }
20
-
21
- send(message: string | ArrayBuffer): void {
22
- if (this._id !== null) _creatorUtils.websocketSend(this._id, message)
23
- }
24
-
25
- close(): void {
26
- if (this._id !== null) {
27
- _creatorUtils.websocketClose(this._id)
28
- this._id = null
29
- }
30
- }
31
-
32
- private _onMessage(channel: string, data: any): void {
33
- if (data !== undefined && data !== null) this.dispatch(channel as keyof SocketEvents, data)
34
- else this.dispatch(channel as keyof SocketEvents)
35
- }
36
- }
@@ -1,218 +0,0 @@
1
- /**
2
- * Client transport for the app backend (docs/backend-plan.md §3.2 / §3.3). The compiler replaces
3
- * every `*.server.ts` module in the client bundle with a stub whose exports are built by the three
4
- * globals below — user code never calls them; it imports the server function and calls it.
5
- *
6
- * export const listNotes = __rpc("<serverUrl>", "notes.server.ts#listNotes")
7
- * export const chat = __channel("<serverUrl>", "chat.server.ts#chat")
8
- * export const db = __serverOnly("schema.server.ts#db")
9
- *
10
- * Wire (implemented by the runner):
11
- * POST <serverUrl>/api/<id> body {"args":[…]}, `authorization: Bearer <session>` when known
12
- * → 200 {"ok":true,"result":…,"session"?:"…"} | {"ok":false,"status":n,"message":"…","session"?:"…"}
13
- * WS <serverUrl>/ws frames: → {t:"hello",session} {t:"sub",id,ch,topic} {t:"unsub",id}
14
- * ← {t:"session",session} {t:"ok",id} {t:"err",id,status,message} {t:"ev",ch,topic,event,data}
15
- * (`ev` is addressed by channel + topic, not by subscription id, so the server can fan one publish out
16
- * to every socket of a topic natively; the client delivers it to each matching subscription.)
17
- *
18
- * The session token is transport-owned (cookie-like): stored under `lecodes.session:<serverUrl>` in
19
- * localStorage, sent on every call and on the socket, replaced whenever a response carries `session`.
20
- * User code never sees it — "am I logged in" is an endpoint (`me()`), see the plan.
21
- */
22
-
23
- import { fetch } from "./fetch"
24
- import { localStorage } from "./storage"
25
- import { WebSocket } from "./net"
26
-
27
- /** Rejection value of a failed endpoint call: `status` mirrors the server's `ApiError`
28
- * (0 = the request itself failed: no network / no server). */
29
- export class RpcError extends Error {
30
- readonly status: number
31
- constructor(status: number, message: string) {
32
- super(message)
33
- this.name = "RpcError"
34
- this.status = status
35
- }
36
- }
37
-
38
- // ───────────────────────────── session token ─────────────────────────────
39
-
40
- const sessionKey = (serverUrl: string) => `lecodes.session:${serverUrl}`
41
- const getSession = (serverUrl: string): string | null => {
42
- try { return localStorage.getItem(sessionKey(serverUrl)) } catch { return null }
43
- }
44
- const setSession = (serverUrl: string, token: string | null) => {
45
- try {
46
- if (token) localStorage.setItem(sessionKey(serverUrl), token)
47
- else localStorage.removeItem(sessionKey(serverUrl))
48
- } catch { /* storage-less host: the session lives for this run only */ }
49
- sockets.get(serverUrl)?.onSessionChanged(token)
50
- }
51
-
52
- // ───────────────────────────── endpoints ─────────────────────────────
53
-
54
- const noServer = (id: string) => new RpcError(0, `Endpoint ${id}: the project has no deployed server (compile with serverUrl)`)
55
-
56
- /** Build the client proxy of endpoint `id`. Positional args travel as a JSON array. */
57
- export const __rpc = (serverUrl: string, id: string) => {
58
- const call = async (...args: unknown[]): Promise<any> => {
59
- if (!serverUrl) throw noServer(id)
60
- const headers: Record<string, string> = { "content-type": "application/json" }
61
- const session = getSession(serverUrl)
62
- if (session) headers.authorization = `Bearer ${session}`
63
- let res
64
- try {
65
- res = await fetch(`${serverUrl}/api/${encodeURIComponent(id)}`, { method: "POST", headers, body: JSON.stringify({ args }) })
66
- } catch (e) {
67
- throw new RpcError(0, `Endpoint ${id}: request failed (${(e as Error)?.message ?? e})`)
68
- }
69
- let body: any
70
- try { body = res.json() } catch { body = null }
71
- res.dispose()
72
- if (body && typeof body === "object" && "session" in body) setSession(serverUrl, body.session ?? null)
73
- if (body && body.ok === true) return body.result
74
- if (body && body.ok === false) throw new RpcError(body.status ?? res.status, body.message ?? "")
75
- throw new RpcError(res.status, `Endpoint ${id}: unexpected response (HTTP ${res.status})`)
76
- }
77
- return call
78
- }
79
-
80
- /** A server-only export (db, models, helpers) referenced from client code: any use throws. */
81
- export const __serverOnly = (id: string): any =>
82
- new Proxy(function () {} as any, {
83
- get: (_t, prop) => { if (prop === "then") return undefined; throw new RpcError(0, `${id} is server-only — it can't be used in client code`) },
84
- apply: () => { throw new RpcError(0, `${id} is server-only — it can't be used in client code`) },
85
- })
86
-
87
- // ───────────────────────────── channels ─────────────────────────────
88
-
89
- type Handlers = Record<string, ((payload: any) => void) | undefined> & {
90
- /** The socket dropped and came back: subscriptions were re-sent, but events in between are lost — refetch. */
91
- reconnect?: () => void
92
- /** The subscription was refused (`onJoin` threw) or the socket can't connect. */
93
- error?: (e: RpcError) => void
94
- }
95
- type Sub = { id: number, ch: string, topic: string, handlers: Handlers, ready: boolean }
96
-
97
- const BACKOFF_MS = [1000, 2000, 5000, 10000, 30000]
98
-
99
- /** One multiplexed socket per server URL, shared by every channel of the app. */
100
- class ChannelSocket {
101
- private ws: WebSocket | null = null
102
- private open = false
103
- private subs = new Map<number, Sub>()
104
- private nextId = 1
105
- private attempts = 0
106
- private timer: ReturnType<typeof setTimeout> | null = null
107
- private closedByUs = false
108
- private everOpened = false
109
- private serverUrl: string
110
-
111
- constructor(serverUrl: string) { this.serverUrl = serverUrl }
112
-
113
- subscribe(ch: string, topic: string, handlers: Handlers): { close(): void } {
114
- const sub: Sub = { id: this.nextId++, ch, topic, handlers, ready: false }
115
- this.subs.set(sub.id, sub)
116
- if (this.open) this.send({ t: "sub", id: sub.id, ch, topic })
117
- else this.connect()
118
- return {
119
- close: () => {
120
- if (!this.subs.delete(sub.id)) return
121
- if (this.open) this.send({ t: "unsub", id: sub.id })
122
- if (this.subs.size === 0) this.shutdown()
123
- },
124
- }
125
- }
126
-
127
- onSessionChanged(token: string | null) {
128
- if (this.open) this.send({ t: "hello", session: token })
129
- }
130
-
131
- private connect() {
132
- if (this.ws || this.timer !== null) return
133
- this.closedByUs = false
134
- const url = this.serverUrl.replace(/^http/, "ws") + "/ws"
135
- const session = getSession(this.serverUrl)
136
- const ws = new WebSocket(url, session ? { authorization: `Bearer ${session}` } : undefined)
137
- this.ws = ws
138
- ws.addEventListener("open", () => {
139
- this.open = true
140
- this.attempts = 0
141
- this.send({ t: "hello", session: getSession(this.serverUrl) })
142
- const reconnected = this.everOpened
143
- this.everOpened = true
144
- for (const sub of this.subs.values()) {
145
- sub.ready = false
146
- this.send({ t: "sub", id: sub.id, ch: sub.ch, topic: sub.topic })
147
- if (reconnected) sub.handlers.reconnect?.()
148
- }
149
- })
150
- ws.addEventListener("message", (data) => {
151
- if (typeof data !== "string") return
152
- let msg: any
153
- try { msg = JSON.parse(data) } catch { return }
154
- this.onFrame(msg)
155
- })
156
- const onDown = () => {
157
- if (this.ws !== ws) return
158
- this.ws = null
159
- this.open = false
160
- if (this.closedByUs || this.subs.size === 0) return
161
- const delay = BACKOFF_MS[Math.min(this.attempts++, BACKOFF_MS.length - 1)]
162
- this.timer = setTimeout(() => { this.timer = null; this.connect() }, delay)
163
- }
164
- ws.addEventListener("close", onDown)
165
- ws.addEventListener("error", onDown)
166
- }
167
-
168
- private onFrame(msg: any) {
169
- switch (msg?.t) {
170
- case "session": setSession(this.serverUrl, msg.session ?? null); break
171
- case "ok": { const s = this.subs.get(msg.id); if (s) s.ready = true; break }
172
- case "err": {
173
- const s = this.subs.get(msg.id)
174
- if (!s) break
175
- this.subs.delete(msg.id)
176
- s.handlers.error?.(new RpcError(msg.status ?? 0, msg.message ?? "subscription refused"))
177
- break
178
- }
179
- case "ev": {
180
- for (const s of this.subs.values()) {
181
- if (s.ch === msg.ch && s.topic === msg.topic) s.handlers[msg.event]?.(msg.data)
182
- }
183
- break
184
- }
185
- }
186
- }
187
-
188
- private send(frame: unknown) { this.ws?.send(JSON.stringify(frame)) }
189
-
190
- private shutdown() {
191
- this.closedByUs = true
192
- if (this.timer !== null) { clearTimeout(this.timer); this.timer = null }
193
- this.ws?.close()
194
- this.ws = null
195
- this.open = false
196
- sockets.delete(this.serverUrl)
197
- }
198
- }
199
-
200
- const sockets = new Map<string, ChannelSocket>()
201
- const socketFor = (serverUrl: string) => {
202
- let s = sockets.get(serverUrl)
203
- if (!s) { s = new ChannelSocket(serverUrl); sockets.set(serverUrl, s) }
204
- return s
205
- }
206
-
207
- export interface ClientChannel<E extends Record<string, unknown> = Record<string, unknown>> {
208
- /** Subscribe to `topic`; handlers are keyed by event name (+ `reconnect`/`error`). Returns `{ close }`. */
209
- subscribe(topic: string, handlers: { [K in keyof E]?: (payload: E[K]) => void } & { reconnect?: () => void, error?: (e: RpcError) => void }): { close(): void }
210
- }
211
-
212
- /** Build the client proxy of channel `id`. */
213
- export const __channel = (serverUrl: string, id: string): ClientChannel<any> => ({
214
- subscribe(topic, handlers) {
215
- if (!serverUrl) throw noServer(id)
216
- return socketFor(serverUrl).subscribe(id, topic, handlers as Handlers)
217
- },
218
- })