lecodes-sdk 2.2.9 → 2.2.10

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 (61) hide show
  1. package/dist/global.d.ts +42 -1
  2. package/dist/types/gl/Camera.d.ts +5 -2
  3. package/dist/types/gl/Node.d.ts +0 -7
  4. package/dist/types/gl/Scene.d.ts +6 -24
  5. package/dist/types/inject.d.ts +5 -1
  6. package/dist/types/plugin.d.ts +27 -0
  7. package/dist/types/plugins/gen/ar/sdk/ar.d.ts +228 -0
  8. package/dist/types/plugins/gen/ar/sdk/ar.gen.d.ts +73 -0
  9. package/dist/types/plugins/gen/ar/sdk/controls.d.ts +28 -0
  10. package/dist/types/plugins/gen/audio-recorder/sdk/audio-recorder.d.ts +43 -0
  11. package/dist/types/plugins/gen/audio-recorder/sdk/audio-recorder.gen.d.ts +47 -0
  12. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +6 -2
  13. package/dist/types/plugins/gen/speech/sdk/speech.d.ts +44 -0
  14. package/dist/types/plugins/gen/speech/sdk/speech.gen.d.ts +39 -0
  15. package/dist/types/runtime/permissions.d.ts +16 -0
  16. package/dist/types/version.d.ts +1 -1
  17. package/dist/types.json +1 -1
  18. package/package.json +1 -1
  19. package/prompts/ar.md +81 -48
  20. package/prompts/dist/2d-game.md +1567 -0
  21. package/prompts/dist/3d-app.md +1712 -0
  22. package/prompts/dist/ar-app.md +1585 -0
  23. package/prompts/dist/concept.md +49 -0
  24. package/prompts/dist/design.md +521 -0
  25. package/prompts/dist/ui-app.md +1175 -0
  26. package/prompts/index.md +3 -1
  27. package/src/animate/tween/Timeline.ts +175 -175
  28. package/src/bridges/device.d.ts +20 -6
  29. package/src/bridges/gl.d.ts +33 -19
  30. package/src/compile/header.ts +11 -4
  31. package/src/gl/Camera.ts +5 -2
  32. package/src/gl/CameraPlace.ts +52 -52
  33. package/src/gl/Light.ts +257 -257
  34. package/src/gl/Mesh.ts +120 -120
  35. package/src/gl/Node.ts +0 -8
  36. package/src/gl/Scene.ts +14 -81
  37. package/src/gl/animation/DynamicBone.ts +482 -482
  38. package/src/gl/scenarios.ts +291 -291
  39. package/src/inject.ts +13 -1
  40. package/src/plugin.ts +45 -2
  41. package/src/plugins/gen/ar/contract.d.ts +118 -0
  42. package/src/plugins/gen/ar/sdk/ar.gen.ts +112 -0
  43. package/src/plugins/gen/ar/sdk/ar.ts +699 -0
  44. package/src/plugins/gen/ar/sdk/controls.ts +148 -0
  45. package/src/plugins/gen/audio-recorder/contract.d.ts +64 -0
  46. package/src/plugins/gen/audio-recorder/sdk/audio-recorder.gen.ts +76 -0
  47. package/src/plugins/gen/audio-recorder/sdk/audio-recorder.ts +113 -0
  48. package/src/plugins/gen/geolocation/contract.d.ts +7 -5
  49. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +6 -2
  50. package/src/plugins/gen/speech/contract.d.ts +67 -0
  51. package/src/plugins/gen/speech/sdk/speech.gen.ts +64 -0
  52. package/src/plugins/gen/speech/sdk/speech.ts +104 -0
  53. package/src/runtime/permissions.ts +40 -0
  54. package/src/server/auth/host.ts +1 -1
  55. package/src/ui/presentable.ts +5 -0
  56. package/src/ui/router.ts +4 -3
  57. package/src/version.ts +1 -1
  58. package/dist/types/gl/controls.d.ts +0 -19
  59. package/dist/types/plugins/permission.d.ts +0 -1
  60. package/src/gl/controls.ts +0 -95
  61. package/src/plugins/permission.ts +0 -8
package/prompts/index.md CHANGED
@@ -34,4 +34,6 @@ implement what you can with the loaded ones.
34
34
  // default export is a SceneHandle: load()/open()) + use/ref/make in the def,
35
35
  // scenario aspects MoveTo/FollowPath/Spin/LookAt/PlayAnimation;
36
36
  // *.editor.ts + EDITOR/InspectorUI are editor-plugin files — never edit or import them
37
- // AR: ARScene (camera passthrough, anchors, placement gestures via addControls)
37
+ // AR: ARScene, Anchor, ImageAnchor, ARPlane — the "ar" plugin (app.json "plugins": { "ar": "*" }): camera
38
+ // passthrough, tracking, planes, hitTest, anchors (root / pose / image), light,
39
+ // captureFrame, placement gestures via addControls
@@ -1,175 +1,175 @@
1
- // `Timeline` (docs/timeline-plan.md §2.4): choreography over the keyframe core. Tracks on any
2
- // number of targets (UI elements, 3D / 2D nodes, lights, cameras) at absolute positions, JS calls
3
- // at times, labels, holds — one clock, one handle. Also the `animateTo` / `animateFrom` bag
4
- // builder every target class delegates to (`tweenBag`).
5
-
6
- import { TweenAnimation, type Animation } from "./Animation"
7
- import { makeValueTrack, type AnimateOptions, type AnimateValue } from "./animateValue"
8
- import { CLOCK_GAME, CLOCK_UI, bagProps, iterationsOf, makeTrack, type Track, type TweenMeta, type TweenSpec, type TweenTarget } from "./spec"
9
-
10
- /** Vector-valued props: an array value is ONE keyframe unless it is an array of vectors. */
11
- const VECTOR_PROPS = new Set(["position", "scale", "quaternion", "eulerAngles", "color"])
12
-
13
- const clockOf = (clock: TweenMeta["clock"], fallback: number): number =>
14
- clock === "game" ? CLOCK_GAME : clock === "ui" ? CLOCK_UI : fallback
15
-
16
- /** @internal Build the tracks of one options bag on one target. `fromCurrent` = animateTo shape. */
17
- export const bagTracks = (target: TweenTarget, bag: Record<string, unknown>, atMs: number, fromCurrent: boolean): Track[] => {
18
- const meta = bag as TweenMeta
19
- const tracks: Track[] = []
20
- for (const [prop, value] of bagProps(bag)) {
21
- const track = makeTrack(target, prop, value, meta, atMs, fromCurrent, VECTOR_PROPS.has(prop))
22
- if (track) tracks.push(track)
23
- }
24
- return tracks
25
- }
26
-
27
- /** @internal The `animateTo` / `animateFrom` implementation: one bag → one animation, played now. */
28
- export const tweenBag = (target: TweenTarget, bag: Record<string, unknown>, fromCurrent: boolean): Animation => {
29
- const meta = bag as TweenMeta
30
- const tracks = bagTracks(target, bag, 0, fromCurrent)
31
- let durationMs = 0
32
- for (const t of tracks) durationMs = Math.max(durationMs, t.atMs + t.durMs)
33
- const spec: TweenSpec = {
34
- clock: clockOf(meta.clock, target._tweenClock),
35
- durationMs, delayMs: 0,
36
- iterations: iterationsOf(meta.loop),
37
- pingPong: meta.loopMode !== "restart",
38
- rate: 1, tracks, calls: [],
39
- }
40
- return new TweenAnimation(spec).play()
41
- }
42
-
43
- export type TimelineOptions = {
44
- /** `'ui'` (default) = wall time; `'game'` follows `Time.scale` and pauses with the game. */
45
- clock?: "ui" | "game",
46
- /** Repeat the whole timeline: `true` = forever, a number = cycles. */
47
- loop?: boolean | number,
48
- loopMode?: "restart" | "ping-pong",
49
- /** Wait before the first play, ms. */
50
- delay?: number,
51
- }
52
-
53
- /** Where a track goes: ms, a label, or `[label, offsetMs]`. Default = the current end (sequencing). */
54
- export type TimelinePosition = number | string | [string, number]
55
-
56
- export type TimelineAddOptions = {
57
- at?: TimelinePosition,
58
- /** With several targets: each starts this many ms after the previous one. */
59
- stagger?: number,
60
- }
61
-
62
- /** A choreography: tracks on many targets at absolute times, JS calls, labels and holds, driven by
63
- * one clock. Build it once, `play()` on every entrance — `play()` restarts from t = 0 and the pose
64
- * at any time is fully defined by the tracks (the first keyframe of a track holds before it
65
- * starts, the last one after it ends). */
66
- export class TimelineImpl extends TweenAnimation {
67
- private _labels = new Map<string, number>()
68
- private _end = 0
69
-
70
- constructor(options: TimelineOptions = {}) {
71
- super({
72
- clock: clockOf(options.clock, CLOCK_UI),
73
- durationMs: 0,
74
- delayMs: options.delay ?? 0,
75
- iterations: iterationsOf(options.loop),
76
- pingPong: options.loopMode !== "restart",
77
- rate: 1, tracks: [], calls: [],
78
- })
79
- }
80
-
81
- private _at(at: TimelinePosition | undefined): number {
82
- if (at === undefined) return this._end
83
- if (typeof at === "number") return at
84
- const name = Array.isArray(at) ? at[0] : at
85
- const offset = Array.isArray(at) ? at[1] : 0
86
- const base = this._labels.get(name)
87
- if (base === undefined) throw new Error(`Timeline: unknown label "${name}"`)
88
- return base + offset
89
- }
90
-
91
- private _grow(endMs: number): void {
92
- if (endMs > this._end) this._end = endMs
93
- this._spec.durationMs = this._end
94
- }
95
-
96
- /** Animate `props` on one target or several (staggered), like `animateTo` — arrays are keyframes,
97
- * `duration` / `easing` / `times` / `commit` apply. The bag's `delay` shifts the track after `at`. */
98
- add(target: TweenTarget | TweenTarget[], props: Record<string, unknown> & TweenMeta, options: TimelineAddOptions = {}): this {
99
- const at = this._at(options.at)
100
- const targets = Array.isArray(target) ? target : [target]
101
- const stagger = options.stagger ?? 0
102
- targets.forEach((t, i) => {
103
- const tracks = bagTracks(t, props, at + i * stagger, true)
104
- for (const tr of tracks) { this._spec.tracks.push(tr); this._grow(tr.atMs + tr.durMs) }
105
- })
106
- return this
107
- }
108
-
109
- /** Like `add` with the `animateFrom` shape: the target's own state is the implicit last keyframe. */
110
- addFrom(target: TweenTarget | TweenTarget[], props: Record<string, unknown> & TweenMeta, options: TimelineAddOptions = {}): this {
111
- const at = this._at(options.at)
112
- const targets = Array.isArray(target) ? target : [target]
113
- const stagger = options.stagger ?? 0
114
- targets.forEach((t, i) => {
115
- const tracks = bagTracks(t, props, at + i * stagger, false)
116
- for (const tr of tracks) { this._spec.tracks.push(tr); this._grow(tr.atMs + tr.durMs) }
117
- })
118
- return this
119
- }
120
-
121
- /** A free-value track (see the `animate()` global): the host evaluates it and calls `onUpdate`
122
- * every frame with the value. */
123
- animate<T extends AnimateValue>(options: AnimateOptions<T>, at?: TimelinePosition): this {
124
- const { track, deliver, slot } = makeValueTrack(options, this._at(at))
125
- this._values.set(slot, deliver)
126
- this._spec.tracks.push(track)
127
- this._grow(track.atMs + track.durMs)
128
- return this
129
- }
130
-
131
- /** Call `fn` when playback crosses `at` (forward or backward), in time order, always before
132
- * finish, never from a previous run. */
133
- call(at: TimelinePosition, fn: () => void): this {
134
- const t = this._at(at)
135
- this._spec.calls.push(t)
136
- this._calls.push(fn)
137
- this._grow(t)
138
- return this
139
- }
140
-
141
- /** Name a time — for `at`, `seek` and callers (`tl.labels.landed`). Default = the current end. */
142
- label(name: string, at?: TimelinePosition): this {
143
- this._labels.set(name, this._at(at))
144
- return this
145
- }
146
-
147
- /** Named times, ms. */
148
- get labels(): Record<string, number> {
149
- const o: Record<string, number> = {}
150
- for (const [k, v] of this._labels) o[k] = v
151
- return o
152
- }
153
-
154
- /** Extend the timeline with stillness after its current end. */
155
- hold(ms: number): this {
156
- this._grow(this._end + Math.max(0, ms))
157
- return this
158
- }
159
-
160
- /** The current end, ms (tracks, calls and holds). */
161
- get end(): number { return this._end }
162
-
163
- /** `seek` also accepts a label. */
164
- override seek(at: number | string): this {
165
- return super.seek(typeof at === "string" ? this._at(at) : at)
166
- }
167
- }
168
-
169
- /** The timeline handle type (see {@link TimelineImpl} for the members). */
170
- export type Timeline = TimelineImpl
171
-
172
- /** Create a timeline — `Timeline()` / `Timeline({ clock: 'game', loop: true })`. */
173
- export function Timeline(options?: TimelineOptions): Timeline {
174
- return new TimelineImpl(options)
175
- }
1
+ // `Timeline` (docs/timeline-plan.md §2.4): choreography over the keyframe core. Tracks on any
2
+ // number of targets (UI elements, 3D / 2D nodes, lights, cameras) at absolute positions, JS calls
3
+ // at times, labels, holds — one clock, one handle. Also the `animateTo` / `animateFrom` bag
4
+ // builder every target class delegates to (`tweenBag`).
5
+
6
+ import { TweenAnimation, type Animation } from "./Animation"
7
+ import { makeValueTrack, type AnimateOptions, type AnimateValue } from "./animateValue"
8
+ import { CLOCK_GAME, CLOCK_UI, bagProps, iterationsOf, makeTrack, type Track, type TweenMeta, type TweenSpec, type TweenTarget } from "./spec"
9
+
10
+ /** Vector-valued props: an array value is ONE keyframe unless it is an array of vectors. */
11
+ const VECTOR_PROPS = new Set(["position", "scale", "quaternion", "eulerAngles", "color"])
12
+
13
+ const clockOf = (clock: TweenMeta["clock"], fallback: number): number =>
14
+ clock === "game" ? CLOCK_GAME : clock === "ui" ? CLOCK_UI : fallback
15
+
16
+ /** @internal Build the tracks of one options bag on one target. `fromCurrent` = animateTo shape. */
17
+ export const bagTracks = (target: TweenTarget, bag: Record<string, unknown>, atMs: number, fromCurrent: boolean): Track[] => {
18
+ const meta = bag as TweenMeta
19
+ const tracks: Track[] = []
20
+ for (const [prop, value] of bagProps(bag)) {
21
+ const track = makeTrack(target, prop, value, meta, atMs, fromCurrent, VECTOR_PROPS.has(prop))
22
+ if (track) tracks.push(track)
23
+ }
24
+ return tracks
25
+ }
26
+
27
+ /** @internal The `animateTo` / `animateFrom` implementation: one bag → one animation, played now. */
28
+ export const tweenBag = (target: TweenTarget, bag: Record<string, unknown>, fromCurrent: boolean): Animation => {
29
+ const meta = bag as TweenMeta
30
+ const tracks = bagTracks(target, bag, 0, fromCurrent)
31
+ let durationMs = 0
32
+ for (const t of tracks) durationMs = Math.max(durationMs, t.atMs + t.durMs)
33
+ const spec: TweenSpec = {
34
+ clock: clockOf(meta.clock, target._tweenClock),
35
+ durationMs, delayMs: 0,
36
+ iterations: iterationsOf(meta.loop),
37
+ pingPong: meta.loopMode !== "restart",
38
+ rate: 1, tracks, calls: [],
39
+ }
40
+ return new TweenAnimation(spec).play()
41
+ }
42
+
43
+ export type TimelineOptions = {
44
+ /** `'ui'` (default) = wall time; `'game'` follows `Time.scale` and pauses with the game. */
45
+ clock?: "ui" | "game",
46
+ /** Repeat the whole timeline: `true` = forever, a number = cycles. */
47
+ loop?: boolean | number,
48
+ loopMode?: "restart" | "ping-pong",
49
+ /** Wait before the first play, ms. */
50
+ delay?: number,
51
+ }
52
+
53
+ /** Where a track goes: ms, a label, or `[label, offsetMs]`. Default = the current end (sequencing). */
54
+ export type TimelinePosition = number | string | [string, number]
55
+
56
+ export type TimelineAddOptions = {
57
+ at?: TimelinePosition,
58
+ /** With several targets: each starts this many ms after the previous one. */
59
+ stagger?: number,
60
+ }
61
+
62
+ /** A choreography: tracks on many targets at absolute times, JS calls, labels and holds, driven by
63
+ * one clock. Build it once, `play()` on every entrance — `play()` restarts from t = 0 and the pose
64
+ * at any time is fully defined by the tracks (the first keyframe of a track holds before it
65
+ * starts, the last one after it ends). */
66
+ export class TimelineImpl extends TweenAnimation {
67
+ private _labels = new Map<string, number>()
68
+ private _end = 0
69
+
70
+ constructor(options: TimelineOptions = {}) {
71
+ super({
72
+ clock: clockOf(options.clock, CLOCK_UI),
73
+ durationMs: 0,
74
+ delayMs: options.delay ?? 0,
75
+ iterations: iterationsOf(options.loop),
76
+ pingPong: options.loopMode !== "restart",
77
+ rate: 1, tracks: [], calls: [],
78
+ })
79
+ }
80
+
81
+ private _at(at: TimelinePosition | undefined): number {
82
+ if (at === undefined) return this._end
83
+ if (typeof at === "number") return at
84
+ const name = Array.isArray(at) ? at[0] : at
85
+ const offset = Array.isArray(at) ? at[1] : 0
86
+ const base = this._labels.get(name)
87
+ if (base === undefined) throw new Error(`Timeline: unknown label "${name}"`)
88
+ return base + offset
89
+ }
90
+
91
+ private _grow(endMs: number): void {
92
+ if (endMs > this._end) this._end = endMs
93
+ this._spec.durationMs = this._end
94
+ }
95
+
96
+ /** Animate `props` on one target or several (staggered), like `animateTo` — arrays are keyframes,
97
+ * `duration` / `easing` / `times` / `commit` apply. The bag's `delay` shifts the track after `at`. */
98
+ add(target: TweenTarget | TweenTarget[], props: Record<string, unknown> & TweenMeta, options: TimelineAddOptions = {}): this {
99
+ const at = this._at(options.at)
100
+ const targets = Array.isArray(target) ? target : [target]
101
+ const stagger = options.stagger ?? 0
102
+ targets.forEach((t, i) => {
103
+ const tracks = bagTracks(t, props, at + i * stagger, true)
104
+ for (const tr of tracks) { this._spec.tracks.push(tr); this._grow(tr.atMs + tr.durMs) }
105
+ })
106
+ return this
107
+ }
108
+
109
+ /** Like `add` with the `animateFrom` shape: the target's own state is the implicit last keyframe. */
110
+ addFrom(target: TweenTarget | TweenTarget[], props: Record<string, unknown> & TweenMeta, options: TimelineAddOptions = {}): this {
111
+ const at = this._at(options.at)
112
+ const targets = Array.isArray(target) ? target : [target]
113
+ const stagger = options.stagger ?? 0
114
+ targets.forEach((t, i) => {
115
+ const tracks = bagTracks(t, props, at + i * stagger, false)
116
+ for (const tr of tracks) { this._spec.tracks.push(tr); this._grow(tr.atMs + tr.durMs) }
117
+ })
118
+ return this
119
+ }
120
+
121
+ /** A free-value track (see the `animate()` global): the host evaluates it and calls `onUpdate`
122
+ * every frame with the value. */
123
+ animate<T extends AnimateValue>(options: AnimateOptions<T>, at?: TimelinePosition): this {
124
+ const { track, deliver, slot } = makeValueTrack(options, this._at(at))
125
+ this._values.set(slot, deliver)
126
+ this._spec.tracks.push(track)
127
+ this._grow(track.atMs + track.durMs)
128
+ return this
129
+ }
130
+
131
+ /** Call `fn` when playback crosses `at` (forward or backward), in time order, always before
132
+ * finish, never from a previous run. */
133
+ call(at: TimelinePosition, fn: () => void): this {
134
+ const t = this._at(at)
135
+ this._spec.calls.push(t)
136
+ this._calls.push(fn)
137
+ this._grow(t)
138
+ return this
139
+ }
140
+
141
+ /** Name a time — for `at`, `seek` and callers (`tl.labels.landed`). Default = the current end. */
142
+ label(name: string, at?: TimelinePosition): this {
143
+ this._labels.set(name, this._at(at))
144
+ return this
145
+ }
146
+
147
+ /** Named times, ms. */
148
+ get labels(): Record<string, number> {
149
+ const o: Record<string, number> = {}
150
+ for (const [k, v] of this._labels) o[k] = v
151
+ return o
152
+ }
153
+
154
+ /** Extend the timeline with stillness after its current end. */
155
+ hold(ms: number): this {
156
+ this._grow(this._end + Math.max(0, ms))
157
+ return this
158
+ }
159
+
160
+ /** The current end, ms (tracks, calls and holds). */
161
+ get end(): number { return this._end }
162
+
163
+ /** `seek` also accepts a label. */
164
+ override seek(at: number | string): this {
165
+ return super.seek(typeof at === "string" ? this._at(at) : at)
166
+ }
167
+ }
168
+
169
+ /** The timeline handle type (see {@link TimelineImpl} for the members). */
170
+ export type Timeline = TimelineImpl
171
+
172
+ /** Create a timeline — `Timeline()` / `Timeline({ clock: 'game', loop: true })`. */
173
+ export function Timeline(options?: TimelineOptions): Timeline {
174
+ return new TimelineImpl(options)
175
+ }
@@ -1,5 +1,5 @@
1
1
  // The device bridge (`_creatorDevice`): what the app runs on — platform facts, haptics, the motion
2
- // sensor, the clipboard, the share sheet, the file picker, the camera permission.
2
+ // sensor, the clipboard, the share sheet, the file picker, the OS permissions.
3
3
  // Part of the host contract: every runtime implements this global (runtime/native in C++, the web
4
4
  // runtimes in TS). A HOST bridge (`@host`, see ./app.d.ts): the native side is the `HostDevice`
5
5
  // table of runtime/native/include/creator-pkg/host.gen.h, generated from this file. Data properties
@@ -70,11 +70,25 @@ declare global {
70
70
  * error. Absent on hosts without a dialog (headless, CREATOR_HIDDEN runs): the SDK resolves
71
71
  * "nothing picked". */
72
72
  openFilePicker?(onComplete: (files: [id: number, name: string, size: number][] | null) => void, onReject: (err: Error) => void, multiple: boolean, accept?: string): void
73
- /** Prompt for camera permission (resolve = granted, reject = denied). Shared by everything that
74
- * needs the camera — ARScene.prepare, QRScanner, CameraView. Optional: hosts whose view
75
- * factory prompts on its own (web getUserMedia) don't implement it and the SDK resolves
76
- * immediately, so callers go through `_requestCameraPermission()`. */
77
- requestCamera?(onComplete: () => void, onReject: () => void): void
73
+
74
+ // ---- Permissions -----------------------------------------------------------------------------
75
+ // The OS permissions, ONE door for the SDK (ARScene.prepare, the camera views), the apps
76
+ // (`permissions.query / request`) and the plugins' generated glue (`@permission` on a method of
77
+ // a contract: the native half asks through the host's same service before the call). `kind` is
78
+ // "camera" | "microphone" | "location" | "speech"; a state is "granted" | "denied" | "prompt"
79
+ // (never asked). Both slots are async because the browser's Permissions API is. Hosts without
80
+ // a dialog (headless, desktop) leave them null and the SDK answers "granted": nothing there
81
+ // stands between an app and a feature; the feature itself says whether it exists.
82
+ /** The state of `kind` without a prompt. `onReject`: an unknown kind. */
83
+ queryPermission?(kind: string, onComplete: (state: string) => void, onReject: (err: string) => void): void
84
+ /** Ask the OS for `kind` — the dialog when it was never asked (the OS decides whether a denied
85
+ * one is asked again) — and complete with the state after. Prompts are queued by the host: two
86
+ * callers get one dialog after another, one kind asked twice while its dialog is up gets one
87
+ * dialog and two answers. `onReject`: an unknown kind, or nowhere to present the dialog. */
88
+ requestPermission?(kind: string, onComplete: (state: string) => void, onReject: (err: string) => void): void
89
+ /** Open the app's own page of the system settings (where a denied permission is turned on).
90
+ * Absent where there is no such page (web). */
91
+ openSettings?(): void
78
92
  }
79
93
  }
80
94
 
@@ -41,9 +41,11 @@ declare global {
41
41
  openScene(sceneId: u32): void
42
42
  /** @custom */
43
43
  closeScene(): void
44
- /** @custom */
44
+ /** Throws — AR is the "ar" plugin since 2.3 (docs/plans/ar-plugin-plan.md decision 6); kept for ONE
45
+ * minor so a bundle of an older SDK says so instead of failing on a missing method, gone in 3.0.
46
+ * @custom */
45
47
  launchAR(sceneId: u32, onComplete: () => void, onReject: () => void): void
46
- /** @custom */
48
+ /** Throws, as launchAR. @custom */
47
49
  stopAR(): void
48
50
  /** @custom */
49
51
  launchVR(sceneId: u32, onComplete: () => void, onReject: (err?: unknown) => void): void
@@ -231,6 +233,21 @@ declare global {
231
233
  setLodBias?(bias: f64): void
232
234
  setMaterialGlobalParameter(sceneId: u32, i: u32, x: f32, y: f32, z: f32, w: f32): void
233
235
  getCameraFov(sceneId: u32, fovType: u8): f64
236
+ /** The pose of the scene's camera: its entity's WORLD matrix (column-major). What a host's gl facade
237
+ * sets for a plugin that tracks the device (AR); an app moves the Camera node instead. Optional. */
238
+ setCameraMatrix?(sceneId: u32, mat: Float32Array): void
239
+ /** A projection the host computes, as a matrix (column-major, OpenGL clip conventions) with the
240
+ * near / far it was built with: it replaces the scene's fov projection until
241
+ * clearCameraProjectionMatrix (an AR framework's camera intrinsics and display crop). Optional:
242
+ * the gl facade's setProjection is a no-op on a host without it. */
243
+ setCameraProjectionMatrix?(sceneId: u32, mat: Float32Array, near: f32, far: f32): void
244
+ clearCameraProjectionMatrix?(sceneId: u32): void
245
+ /** The scene's background: texture `textureId` behind everything, filling the viewport, through
246
+ * the affine map `uv` (6 floats, row-major [a b tx c d ty]) from the viewport's normalized
247
+ * coordinates (0,0 top-left) to the texture's; 0xFFFFFFFF = none (the sky comes back). The engine
248
+ * draws it (creator.h setSceneBackground); the runtime registers its built-in material from the
249
+ * materials archive. Optional. @custom */
250
+ setSceneBackground?(sceneId: u32, textureId: u32, uv: Float32Array): void
234
251
  /** The scene camera's projection: VERTICAL fov in degrees + near/far clip distances (defaults
235
252
  * 60 / 0.01 / 1000). Aspect stays host-owned — the host STORES these per scene and re-applies
236
253
  * them whenever the viewport changes, so one call outlives every resize. Optional: hosts that
@@ -330,9 +347,11 @@ declare global {
330
347
  setUniformArray(materialId: u32, uniform: string, value: Float32Array): void
331
348
  setUniformNull(materialId: u32, uniform: string): void
332
349
 
333
- /** @custom */
350
+ /** The scene's camera = this entity (every Scene's Camera attaches through it; the name is a
351
+ * relic of the AR it first served — it is not AR). @custom */
334
352
  attachCameraToAR(sceneId: u32, cameraEntityId: u32, onProjectionChange: (mat: Float32Array, fov: number) => void): void
335
- /** @custom */
353
+ /** The scene view's [width, height] in logical units — the units of the scene's touches on every
354
+ * host: the viewport over the density the host set (Camera.displaySize). @custom */
336
355
  getDisplaySize(): Float32Array
337
356
 
338
357
  setVisible(entityId: u32, visible: boolean): void
@@ -1106,11 +1125,11 @@ declare global {
1106
1125
 
1107
1126
  hasMesh(entityId: u32): boolean
1108
1127
 
1109
- /** @custom */
1128
+ /** Throws, as launchAR (decision 6 of docs/plans/ar-plugin-plan.md). @custom */
1110
1129
  createARController(sceneId: u32, cameraId: u32, mode: string, onTrack: (entityId: number, track: boolean) => void): void
1111
- /** @custom */
1130
+ /** Throws, as launchAR. @custom */
1112
1131
  createRootAnchor(sceneId: u32): u32
1113
- /** @custom */
1132
+ /** Throws, as launchAR. @custom */
1114
1133
  createAnchor(sceneId: u32, physicalWidth: f64, systemId: bytes): u32
1115
1134
 
1116
1135
  // Particles. All emitter/curve config crosses as ONE Float32Array of [tag, payloadLen,
@@ -1146,15 +1165,19 @@ declare global {
1146
1165
  getNoise2D(noiseId: u32, x: f32, y: f32): f64
1147
1166
  getNoise3D(noiseId: u32, x: f32, y: f32, z: f32): f64
1148
1167
 
1149
- /** @custom */
1168
+ /** The scene's picture as a PNG File: the composite of its next frame (the scene and its overlays,
1169
+ * under the platform's UI — creator.h readPixels), encoded by the core, in the host's buffer store;
1170
+ * settled through the dispatch queue. Rejects when the scene is not the one presented, or it
1171
+ * changes before the frame. @custom */
1150
1172
  captureImage(sceneId: number, onComplete: (bufferId: number, name: string, size: number) => void, onReject: () => void): void
1151
1173
  }
1152
1174
  }
1153
1175
 
1154
1176
  /** The 3D HOST's side of the contract — what the engine needs from the platform around Filament
1155
1177
  * (`HostGL` of creator-pkg/host.gen.h): the GL context to share, the scene view's lifecycle, image
1156
- * decoding into textures, AR / VR sessions. OPTIONAL as a whole (a UI-only build has no 3D); every
1157
- * slot but `createTexture` is optional — the runtime skips what a host does not fill.
1178
+ * decoding into textures, VR sessions. OPTIONAL as a whole (a UI-only build has no 3D); every
1179
+ * slot but `createTexture` is optional — the runtime skips what a host does not fill. AR is not
1180
+ * here: it is a plugin, which drives the engine through the host's gl facade (docs/plans/ar-plugin-plan.md).
1158
1181
  * @host gl HostGL @optional */
1159
1182
  export interface HostGL {
1160
1183
  /** The host's GL context Filament shares with (a desktop master context, the XR context on Quest);
@@ -1173,18 +1196,9 @@ export interface HostGL {
1173
1196
  createTexture(systemId: bytes, flags: u32, onComplete: (textureId: u32, width: i32, height: i32) => void, onReject: (err: string) => void): void
1174
1197
  /** Import a media player's frames as an engine texture (`_creator.createMediaPlayerTexture`); 0 = none. */
1175
1198
  createMediaPlayerTexture?(playerId: i32): i32
1176
- /** Start an AR session for the scene; settle one callback through the dispatch bridge. */
1177
- launchAR?(sceneId: u32, onComplete: () => void, onReject: (err: string) => void): void
1178
- stopAR?(): void
1179
1199
  /** Start a VR (OpenXR) session for the scene; absent = "VR is not supported on this device". */
1180
1200
  launchVR?(sceneId: u32, onComplete: () => void, onReject: (err: string) => void): void
1181
1201
  stopVR?(): void
1182
- /** Bind the AR camera of `sceneId` to `cameraId` in `mode` ("world" / "face" / …). */
1183
- createARController?(sceneId: u32, cameraId: u32, mode: string): void
1184
- /** The AR root anchor entity of a scene. */
1185
- createRootAnchor?(sceneId: u32, entityId: u32): void
1186
- /** An image anchor: track the image in host buffer `systemId` (`physicalWidth` in metres) on `entityId`. */
1187
- createAnchor?(sceneId: u32, entityId: u32, physicalWidth: f32, systemId: bytes): void
1188
1202
  }
1189
1203
 
1190
1204
  export {}
@@ -51,7 +51,7 @@ export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}
51
51
  // bundle, and nothing 2D works without a Scene2D anyway.
52
52
  if (code.includes("_creator2d.createScene")) header += `// 2d: enable\n`
53
53
 
54
- if (code.includes("_creator.createARController")) header += `// ar: enable\n`
54
+ if (usesAR(code, opts.plugins)) header += `// ar: enable\n`
55
55
 
56
56
  // Short, so with the engine lines: a host reads it before it registers anything.
57
57
  if (opts.plugins?.length) header += `// plugins: ${opts.plugins.join(";")}\n`
@@ -82,9 +82,16 @@ export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}
82
82
  return header
83
83
  }
84
84
 
85
- /** Same engine signals the header uses, for callers that need the project kind (e.g. icon type). */
86
- export const detectProjectKind = (code: string): "ar" | "ui" | "2d" | "3d" => {
87
- if (code.includes("_creator.createARController")) return "ar"
85
+ /** `// ar: enable` (the platform's backend reads it: an AR app is offered to the LeCodes app): AR is the
86
+ * "ar" plugin since 2.3 (docs/plans/ar-plugin-plan.md) — an app that lists it and makes a 3D scene.
87
+ * Its ARScene is the plugin's wrapper, whose calls name no engine member a bundle could be read for. */
88
+ const usesAR = (code: string, plugins: readonly string[] | undefined): boolean =>
89
+ !!plugins?.includes("ar") && code.includes("_creator.createScene")
90
+
91
+ /** Same engine signals the header uses, for callers that need the project kind (e.g. icon type).
92
+ * `plugins` = the app's (app.json), what tells an AR app. */
93
+ export const detectProjectKind = (code: string, plugins?: readonly string[]): "ar" | "ui" | "2d" | "3d" => {
94
+ if (usesAR(code, plugins)) return "ar"
88
95
  if (code.includes("_creator2d.createScene")) return "2d"
89
96
  if (!code.includes("_creator.")) return "ui"
90
97
  return "3d"
package/src/gl/Camera.ts CHANGED
@@ -173,12 +173,15 @@ export class Camera extends Node {
173
173
  return this
174
174
  }
175
175
 
176
+ /** The scene view's [width, height] in logical units — the units of the scene's touches
177
+ * (`clientX` / `clientY`) and of `getRay`, on every host; × `device.pixelRatio` for device px.
178
+ * [0, 0] before the scene has a viewport. */
176
179
  get displaySize(): [number, number] {
177
180
  const s = _creator.getDisplaySize()
178
181
  return [ s[0], s[1] ]
179
182
  }
180
183
 
181
- /** World-space ray direction through a screen pixel. */
184
+ /** World-space ray direction through a point of the scene view (logical units, as a touch's). */
182
185
  getViewDirection(screenX: number, screenY: number): Vec3 {
183
186
  if (this._sceneId < 0) throw new Error("Camera is not attached to a scene")
184
187
  const v = new Float32Array(3)
@@ -186,7 +189,7 @@ export class Camera extends Node {
186
189
  return new Vec3(v[0], v[1], v[2])
187
190
  }
188
191
 
189
- /** A pick ray from the camera through a screen pixel. */
192
+ /** A pick ray from the camera through a point of the scene view (logical units, as a touch's). */
190
193
  getRay(screenX: number, screenY: number): Ray {
191
194
  return new Ray(this.worldPosition, this.getViewDirection(screenX, screenY))
192
195
  }