lecodes-sdk 2.4.1 → 2.4.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 (51) hide show
  1. package/dist/types/core/Aspect.d.ts +34 -5
  2. package/dist/types/g2/OneWay2D.d.ts +0 -1
  3. package/dist/types/g2/Physics2D.d.ts +8 -6
  4. package/dist/types/g2/Scene2D.d.ts +1 -1
  5. package/dist/types/g2/Shape2D.d.ts +43 -15
  6. package/dist/types/gl/Model.d.ts +3 -2
  7. package/dist/types/gl/Scene.d.ts +1 -1
  8. package/dist/types/gl/animation/DynamicBone.d.ts +99 -35
  9. package/dist/types/gl/animation/IK.d.ts +0 -1
  10. package/dist/types/gl/nav/NavAgent.d.ts +3 -2
  11. package/dist/types/gl/physics/Physics.d.ts +4 -5
  12. package/dist/types/gl/physics/Shape.d.ts +40 -15
  13. package/dist/types/gl/vehicle/Vehicle.d.ts +54 -35
  14. package/dist/types/gl/vehicle/Wheel.d.ts +31 -10
  15. package/dist/types/plugins/gen/ar/contract.d.ts +152 -0
  16. package/dist/types/plugins/gen/ar/sdk/ar.d.ts +47 -21
  17. package/dist/types/plugins/gen/ar/sdk/ar.gen.d.ts +17 -3
  18. package/dist/types/plugins/gen/audio-recorder/contract.d.ts +64 -0
  19. package/dist/types/plugins/gen/browser/contract.d.ts +108 -0
  20. package/dist/types/plugins/gen/camera/contract.d.ts +27 -0
  21. package/dist/types/plugins/gen/geolocation/contract.d.ts +52 -0
  22. package/dist/types/plugins/gen/map/contract.d.ts +144 -0
  23. package/dist/types/plugins/gen/push/contract.d.ts +83 -0
  24. package/dist/types/plugins/gen/qr-scanner/contract.d.ts +16 -0
  25. package/dist/types/plugins/gen/speech/contract.d.ts +67 -0
  26. package/dist/types/plugins/gen/webrtc/contract.d.ts +342 -0
  27. package/dist/types/version.d.ts +1 -1
  28. package/dist/types.json +1 -1
  29. package/package.json +1 -1
  30. package/src/bridges/gl.d.ts +5 -3
  31. package/src/compile/compileProject.ts +45 -10
  32. package/src/compile/index.ts +7 -0
  33. package/src/compile/webHalf.ts +255 -0
  34. package/src/core/Aspect.ts +118 -35
  35. package/src/g2/OneWay2D.ts +0 -2
  36. package/src/g2/Physics2D.ts +8 -16
  37. package/src/g2/Scene2D.ts +1 -1
  38. package/src/g2/Shape2D.ts +51 -22
  39. package/src/gl/Model.ts +3 -2
  40. package/src/gl/Scene.ts +1 -1
  41. package/src/gl/animation/DynamicBone.ts +549 -486
  42. package/src/gl/animation/IK.ts +0 -2
  43. package/src/gl/nav/NavAgent.ts +14 -12
  44. package/src/gl/physics/Physics.ts +4 -5
  45. package/src/gl/physics/Shape.ts +49 -23
  46. package/src/gl/vehicle/Vehicle.ts +102 -58
  47. package/src/gl/vehicle/Wheel.ts +45 -11
  48. package/src/plugins/gen/ar/contract.d.ts +35 -1
  49. package/src/plugins/gen/ar/sdk/ar.gen.ts +25 -6
  50. package/src/plugins/gen/ar/sdk/ar.ts +102 -35
  51. package/src/version.ts +1 -1
@@ -131,21 +131,28 @@ export declare class Vehicle extends Aspect<"vehicle", Node> {
131
131
  * without physics or on an older host. A host that has it can still refuse a car's settings
132
132
  * (`id` stays 0, with a warning). */
133
133
  static get supported(): boolean;
134
- /** Chassis mass in kg (default 1200). Wheels are massless in this model. */
135
- mass: number;
134
+ private _mass;
135
+ /** Chassis mass in kg (default 1200). Wheels are massless in this model. Structural: a write on a
136
+ * live car warns and keeps the built mass. */
137
+ get mass(): number;
138
+ set mass(v: number);
136
139
  /** Centre of mass height in chassis space (metres, negative = lower). Left out, it drops to halfway
137
- * down the chassis shape — a car with its mass at the shape's centre tips over. */
140
+ * down the chassis shape — a car with its mass at the shape's centre tips over. Structural. */
138
141
  centerOfMass?: number;
139
142
  /** Which axle the engine drives (default "rear") — the default for every wheel's own `driven`
140
- * share: 1 on that axle, 0 elsewhere. `none` = no drive at all (a trailer, a pushed cart). */
143
+ * share: 1 on that axle, 0 elsewhere. `none` = no drive at all (a trailer, a pushed cart). Structural. */
141
144
  driveAxle: DriveLayout;
145
+ private _differential;
142
146
  /** Differential lock across each driven axle. `locked` is the drift setup — both wheels turn
143
- * together, so the pair lets go as a pair. Live: re-configure the aspect to switch it mid-drive. */
144
- differential: DifferentialMode | number;
147
+ * together, so the pair lets go as a pair. Live: switch it mid-drive. */
148
+ get differential(): DifferentialMode | number;
149
+ set differential(v: DifferentialMode | number);
150
+ private _antiRoll;
145
151
  /**
146
152
  * Anti-roll bars between each axle's wheels — what stops a car rolling onto its inside wheels in a
147
153
  * fast corner. `true` (default) is a bar 0.01 as stiff as the wheel's own spring; a number is that
148
- * ratio directly, and `0` / `false` removes the bars.
154
+ * ratio directly, and `0` / `false` removes the bars. Live: the stiffness, on axles built with a
155
+ * bar (`false` at build = no bar to stiffen later).
149
156
  *
150
157
  * **The numbers are far smaller than they look and the gain is steep** — Jolt's bar feeds
151
158
  * `lengthDifference × stiffness × dt` as a bias into the suspension's own soft constraint, so 4%
@@ -155,24 +162,43 @@ export declare class Vehicle extends Aspect<"vehicle", Node> {
155
162
  * This is the knob for how much a car visibly LEANS, not for wheel lift: bars change how far the
156
163
  * body tips, not how much load transfers. For lift, lower `centerOfMass`.
157
164
  */
158
- antiRoll: boolean | number;
159
- /** Pitch/roll limit in degrees (default 60) before the constraint rights the car. 180 turns it off. */
160
- maxTilt: number;
165
+ get antiRoll(): boolean | number;
166
+ set antiRoll(v: boolean | number);
167
+ private _maxTilt;
168
+ /** Pitch/roll limit in degrees (default 60) before the constraint rights the car. 180 turns it off. Live. */
169
+ get maxTilt(): number;
170
+ set maxTilt(v: number);
171
+ private _friction;
161
172
  /** Friction of the CHASSIS body itself — how it slides when it lands on its roof or side. Nothing
162
- * to do with the tires; those are the `Wheel`'s `tire` × the ground's friction. */
163
- friction: number;
173
+ * to do with the tires; those are the `Wheel`'s `tire` × the ground's friction. Live, at once. */
174
+ get friction(): number;
175
+ set friction(v: number);
176
+ private _clutchStrength;
164
177
  /** Strength of the clutch when fully engaged (default 10). Small values slip; large ones weld the
165
- * engine to the wheels. `clutch` (the input) scales it 0..1. */
166
- clutchStrength: number;
178
+ * engine to the wheels. `clutch` (the input) scales it 0..1. Live. */
179
+ get clutchStrength(): number;
180
+ set clutchStrength(v: number);
181
+ private _clutchCapacity;
167
182
  /** The clutch's torque CAPACITY, N·m (default 0 = none: Jolt's viscous clutch alone, torque =
168
183
  * `clutchStrength` × slip speed, no ceiling — rigid at any real slip). Set it and the clutch
169
184
  * SLIPS at this much torque × `clutch` — a dry clutch, emulated a physics step at a time: a
170
185
  * launch can be feathered, and a dumped clutch in too high a gear drags the engine down instead
171
- * of lurching. A road car's is ~1.5× the engine's peak torque. */
172
- clutchCapacity: number;
173
- engine: EngineConfig;
174
- steering: SteeringConfig;
175
- aero: AeroConfig;
186
+ * of lurching. A road car's is ~1.5× the engine's peak torque. Live. */
187
+ get clutchCapacity(): number;
188
+ set clutchCapacity(v: number);
189
+ private _engine;
190
+ /** The engine (`EngineConfig`). Live: `car.vehicle.engine.torque = 800`, or assign a whole config. */
191
+ get engine(): EngineConfig;
192
+ set engine(v: EngineConfig);
193
+ private _steering;
194
+ /** The steering (`SteeringConfig`). Live, one level deep: `feedback` and `driver` are each
195
+ * replaced as a whole (`steering.driver = { maxTorque: 90 }`), not edited inside. */
196
+ get steering(): SteeringConfig;
197
+ set steering(v: SteeringConfig);
198
+ private _aero;
199
+ /** Aerodynamics (`AeroConfig`). Live: a wing that deploys is `car.vehicle.aero.downforce = 0.6`. */
200
+ get aero(): AeroConfig;
201
+ set aero(v: AeroConfig);
176
202
  /** Scene-editor inspector: the flat, tunable half. The grouped objects are edited in code. */
177
203
  static fields: FieldMeta<Vehicle>;
178
204
  static editor: {
@@ -194,22 +220,14 @@ export declare class Vehicle extends Aspect<"vehicle", Node> {
194
220
  private _input;
195
221
  private _sent;
196
222
  private _dirty;
197
- private _retunePending;
223
+ private _rebuildPending;
198
224
  onAttach(): void;
199
225
  onDetach(): void;
200
- /**
201
- * Re-configuring an attached vehicle retunes it in place — the way a drift button switches the
202
- * differential without the car so much as blinking:
203
- *
204
- * car.aspect(Vehicle, { differential: drift ? 'locked' : 'lsd' })
205
- *
206
- * Live: `differential`, `engine`, `steering`, `aero`, `maxTilt`, `friction`, `clutchStrength`,
207
- * `clutchCapacity`, `antiRoll` (its stiffness, on axles that have a bar), and every `Wheel`'s
208
- * `tire`. Everything else describes the car's STRUCTURE — `mass`, `centerOfMass`, which wheels
209
- * exist and where, their suspension, the driven shares — and is baked into the constraint at
210
- * attach; to change one of those, re-attach the aspect.
211
- */
212
- onReconfigure(): void;
226
+ /** A live value changed on the car or a wheel: the tuning half of the blob is re-sent in
227
+ * `flush()`, one send however many values changed this frame. */
228
+ protected flush(): void;
229
+ private _setup;
230
+ private _teardown;
213
231
  /** Native vehicle id (0 until attached / no physics support). */
214
232
  get id(): number;
215
233
  /** The chassis rigid body id (for `physicsApplyImpulse`-style calls). */
@@ -239,8 +257,9 @@ export declare class Vehicle extends Aspect<"vehicle", Node> {
239
257
  /**
240
258
  * EARLY phase: hand this frame's inputs to the engine, before the step. The vector is sent when
241
259
  * anything changed — and every frame while a throttle, steer or brake is non-zero, because that is
242
- * what keeps a driven car awake (an idle car with zero input sleeps). The pending re-tune (a
243
- * config or tire change) goes first, so the step runs on the new numbers.
260
+ * what keeps a driven car awake (an idle car with zero input sleeps). A pending re-tune (a live
261
+ * value of the car or a wheel changed) went out in `flush()`, before this phase, so the step runs
262
+ * on the new numbers.
244
263
  */
245
264
  protected updateBefore(): void;
246
265
  /** Teleport upright and clear all motion (velocity, engine rpm, wheel spin). Defaults to the
@@ -70,12 +70,22 @@ export declare const Tire: {
70
70
  scale: (t: Tire, k: number) => Tire;
71
71
  };
72
72
  export declare function resolveTire(tire: Tire): ResolvedTire;
73
+ /**
74
+ * One wheel. LIVE: `radius`, `suspension.stiffness` / `damping` and `tire` — write them and the car
75
+ * retunes before the next step (a tire losing air: `wheel.radius *= 0.9`, `wheel.suspension.stiffness
76
+ * *= 0.7`, `wheel.tire = Tire.scale(wheel.tire, 0.5)`). STRUCTURAL: `width`, `suspension.travel`, `axle`,
77
+ * `steer`, `driven` — baked when the car is built; to change one, build the wheel again
78
+ * (`hub.aspect(Wheel, {…})` — the car rebuilds itself around it, its motion reset).
79
+ */
73
80
  export declare class Wheel extends Aspect<"wheel", Node> {
74
81
  static readonly aspect = "wheel";
75
82
  /** Wheel radius in metres. Default: measured from the node's rendered bounds, else 0.35. Too small
76
- * and the car scrapes, too large and it floats. */
77
- radius?: number;
78
- /** Wheel width in metres. Default: measured, else 0.25. */
83
+ * and the car scrapes, too large and it floats. Live: the ROLLING radius — the suspension hangs
84
+ * where it was built, so a smaller one lowers that corner of the car, the way a tire losing air
85
+ * does; the wheel's inertia keeps the radius it was built with. */
86
+ get radius(): number | undefined;
87
+ set radius(v: number | undefined);
88
+ /** Wheel width in metres. Default: measured, else 0.25. Structural. */
79
89
  width?: number;
80
90
  /** Axle group, front to back. Default: derived from Z. Set it for a car whose wheels are not in neat
81
91
  * left/right pairs — an axle's wheels are paired for the differential and the anti-roll bar. */
@@ -87,10 +97,19 @@ export declare class Wheel extends Aspect<"wheel", Node> {
87
97
  * split their ratio, so `0.6` / `0.4` front-to-rear is `driven: 0.3` on each front wheel and
88
98
  * `0.2` on each rear. Structural: baked at attach. */
89
99
  driven?: number;
90
- suspension: WheelSuspension;
91
- /** The tire (default `Tire.road()`). Live: re-assign it or re-configure the aspect and the car
92
- * retunes in place — a grip slider, a tire that heats up. */
93
- tire: Tire;
100
+ private _suspension;
101
+ /** The suspension. Live: `stiffness` and `damping` (a softer corner sags and rolls more) — write a
102
+ * field (`wheel.suspension.stiffness = 1`) or assign a whole object (what it leaves out is the
103
+ * default). `travel` is structural. */
104
+ get suspension(): WheelSuspension;
105
+ set suspension(v: WheelSuspension);
106
+ private _tire;
107
+ /** The tire (default `Tire.road()`). Live: assign one (`wheel.tire = Tire.drift()`, or
108
+ * `Tire.scale(wheel.tire, 0.5)` for a grip slider, a tire that heats up) or write a field
109
+ * (`wheel.tire.traction = 1`); a curve is replaced, never edited in place. A tire assigned without
110
+ * `traction` / `circle` gets `road`'s. */
111
+ get tire(): Tire;
112
+ set tire(v: Tire);
94
113
  /** INPUT: brake torque on this wheel THIS frame, N·m (default 0). Sticky, like `Vehicle.throttle`:
95
114
  * write it every frame from a driver system's `updateBefore` — the foot brake's share, the
96
115
  * handbrake's 4500 on a rear wheel, an ABS's pulses — and the same physics step applies it. A
@@ -102,10 +121,12 @@ export declare class Wheel extends Aspect<"wheel", Node> {
102
121
  static editor: {
103
122
  rebuild: boolean;
104
123
  };
124
+ /** A wheel built under a car that already runs (`hub.aspect(Wheel, {…})` on a live car): the car
125
+ * rebuilds itself around the new set of wheels before the next step. */
126
+ onAttach(): void;
105
127
  onDetach(): void;
106
- /** Re-configuring a wheel on a live car retunes the car: the tire is live; size, suspension, the
107
- * driven share and the axle are structural (re-attach the Vehicle). */
108
- onReconfigure(): void;
128
+ /** A live value changed (the tire, the radius, the spring): the car retunes in the same flush. */
129
+ protected flush(): void;
109
130
  private _state;
110
131
  /** Touching the ground this step. */
111
132
  get contact(): boolean;
@@ -0,0 +1,152 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/ar/contract.d.ts — do not edit here.
2
+ // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
+ // ar — world tracking over the device camera: ARKit on iOS, ARCore on Android, the camera under the
4
+ // scene turned by the sensors on the web. The app's side is ARScene (sdk/ar.ts), a Scene whose
5
+ // camera and background the session drives every frame through the host's gl facade.
6
+ // A session is ONE AR scene: ARScene opens it with the scene and its camera entity as params and
7
+ // closes it with the scene. `@permission camera` on start: the host asks, the halves never do.
8
+ // What a session finds in the camera's picture and drives an entity to: an image target (a printed
9
+ // picture, by its bytes) and, since contract version 2, a QR code (a printed symbol, by its text) —
10
+ // both through the `image` event. Codes are the web's (WebXR) first: the native halves take them and
11
+ // say they track none yet.
12
+ import type { Service, File, Entity, Scene3D, f64 } from "../../../plugin"
13
+
14
+ export type ARMode = "world" | "images"
15
+ export type PlaneDetection = "horizontal" | "vertical" | "both" | "none"
16
+ export type WorldAlignment = "gravity" | "gravityAndHeading"
17
+ export type TrackingState = "notAvailable" | "limited" | "normal"
18
+ export type TrackingReason = "initializing" | "excessiveMotion" | "insufficientFeatures" | "relocalizing"
19
+ export type PlaneType = "horizontal" | "vertical"
20
+ export type PlaneChange = "added" | "updated" | "removed"
21
+ export type HitTarget = "planes" | "estimated" | "any"
22
+ export type ARErrorCode = "unavailable" | "camera" | "failed"
23
+
24
+ export interface Vec3 { x: f64, y: f64, z: f64 }
25
+ export interface Quat { x: f64, y: f64, z: f64, w: f64 }
26
+ /** A pose in the scene's world: metres, Y up. */
27
+ export interface Pose { position: Vec3, rotation: Quat }
28
+
29
+ export interface ImageTarget {
30
+ image: File
31
+ /** The printed width, metres: the size the session starts from (the height by the image's
32
+ * proportions) — the framework refines it (the `image` event's `extent`). */
33
+ physicalWidth: f64
34
+ /** The entity that follows the image (its Anchor node). */
35
+ entity: Entity
36
+ }
37
+
38
+ /** A printed QR code the session tracks: found by its text in the camera's picture; while
39
+ * tracked its entity is driven like an image target's — a rigid pose in metres, no scale — the
40
+ * symbol's centre, Y out of it, X along its top edge, Z towards its bottom. Web (WebXR) first;
41
+ * the native halves later. */
42
+ export interface QRTarget {
43
+ /** The code's text: a symbol with another text is not this target. "" = ANY code (the first
44
+ * one decoded): a test, or a plaque whose text is not known to the app. */
45
+ text: string
46
+ /** The printed side of the symbol (its modules, no quiet zone), metres. */
47
+ size: f64
48
+ /** The entity that follows the code (its Anchor node). */
49
+ entity: Entity
50
+ }
51
+
52
+ export interface StartOptions {
53
+ /** Default "world". "images": image tracking only (no planes, no hit test; iOS runs
54
+ * ARImageTrackingConfiguration, which is lighter). */
55
+ mode?: ARMode
56
+ /** Default "horizontal". Ignored in "images". */
57
+ planes?: PlaneDetection
58
+ images?: ImageTarget[]
59
+ /** The QR codes to track (in "world" and in "images"); a half without them says so and drives
60
+ * none. @since 2 */
61
+ codes?: QRTarget[]
62
+ /** Default "gravity". "gravityAndHeading": -Z points north (the compass). */
63
+ worldAlignment?: WorldAlignment
64
+ /** Deliver `light` events. Default false. */
65
+ lightEstimation?: boolean
66
+ /** Default true. */
67
+ autoFocus?: boolean
68
+ }
69
+
70
+ export interface HitResult {
71
+ pose: Pose
72
+ /** "plane": on a detected plane (`plane` = its id); "estimated": on a point of the feature
73
+ * cloud / an estimated plane. */
74
+ type: "plane" | "estimated"
75
+ plane?: f64
76
+ /** Metres from the camera. */
77
+ distance: f64
78
+ }
79
+
80
+ /** Both are ARScene's, always sent. Optional because a channel's params are (the generator's rule: a
81
+ * param a half cannot read has no one to reject to); a session opened without them rejects `start`
82
+ * with "failed". */
83
+ export interface ARParams {
84
+ scene?: Scene3D
85
+ /** The scene's camera entity: the session sets its matrix and the scene's projection each frame. */
86
+ camera?: Entity
87
+ }
88
+
89
+ export interface AREvents {
90
+ /** The tracking quality; the first event of a session. */
91
+ tracking: { state: TrackingState, reason?: TrackingReason }
92
+ /** A detected plane: added, moved / grown (throttled: a change over 1 cm, at most 10 per second),
93
+ * or gone (`mergedInto` = the plane that absorbed it, ARCore). `pose` = the plane's centre, its
94
+ * Y the normal; `extent` = [width, height] metres; `polygon` = the boundary as x,z pairs in the
95
+ * plane's space. */
96
+ plane: { change: PlaneChange, id: f64, type: PlaneType, pose: Pose, extent: f64[], polygon: f64[], mergedInto?: f64 }
97
+ /** An image target found, lost, or its size refined. While tracked its entity is driven: a rigid
98
+ * pose in metres, no scale — the image's centre, Y out of it, the image in XZ (X along its width,
99
+ * Z towards its bottom). `extent` = [width, height] metres, the framework's estimate (sent again
100
+ * when it changes by a millimetre, at most 10 per second); the last one when lost. A QR target's
101
+ * comes the same way (its frame: X along the symbol's top edge), `extent` = [size, size]. */
102
+ image: { entity: Entity, tracked: boolean, extent: f64[] }
103
+ /** An anchor made by `anchor()` lost or regained after a relocalization. */
104
+ anchor: { entity: Entity, tracked: boolean }
105
+ /** The estimated light (`lightEstimation`): lumens-ish ambient intensity (1000 = ARKit's
106
+ * neutral), colour temperature in kelvin; at most 10 per second. */
107
+ light: { intensity: f64, temperature: f64 }
108
+ /** The session failed and is over: "unavailable" (no ARKit / ARCore support, ARCore not
109
+ * installed and declined), "camera" (the camera could not be used), "failed". */
110
+ error: { code: ARErrorCode, message: string }
111
+ /** The session is over, and not by `stop()`: the user left it ("user" — the browser's back / exit
112
+ * of an immersive WebXR session) or the system took it ("system"). Not an error: ARScene closes
113
+ * the scene. ARKit and ARCore never end a session by themselves (an interruption is `tracking`
114
+ * limited): the native halves do not send it. @since 2 */
115
+ ended: { reason: "user" | "system" }
116
+ }
117
+
118
+ export interface AR extends Service<"ar", ARParams, AREvents> {
119
+ /** Start tracking; resolves at the first frame. A second start rejects "busy".
120
+ * @permission camera
121
+ * @rejects busy unavailable failed */
122
+ start(options?: StartOptions): Promise<void>
123
+ /** Stop tracking: the camera is released, the scene's projection and background cleared. Not
124
+ * running is not an error. */
125
+ stop(): Promise<void>
126
+ /** Replace the set of image targets of a RUNNING session without restarting it (the tracking, the
127
+ * planes and the anchors stay); an image no longer in the set stops driving its entity (its
128
+ * `image` event says `tracked: false` once if it was tracked). Before `start`, the images are
129
+ * `StartOptions.images`.
130
+ * @rejects notRunning failed */
131
+ setImages(images: ImageTarget[]): Promise<void>
132
+ /** The twin of `setImages` for QR codes: replace the set of QR targets of a RUNNING session
133
+ * without restarting it; a code no longer in the set stops driving its entity (`tracked: false`
134
+ * once if it was tracked). Before `start`, the codes are `StartOptions.codes`. A half that tracks
135
+ * no codes yet (the native ones) rejects "failed".
136
+ * @since 2
137
+ * @rejects notRunning failed */
138
+ setCodes(codes: QRTarget[]): Promise<void>
139
+ /** What is under a point of the scene's view — in its touches' space (a scene touch's `clientX` /
140
+ * `clientY`: the units of the gl facade's viewport) — nearest first. Empty when nothing is, or on
141
+ * a host without surfaces (the web).
142
+ * @rejects notRunning */
143
+ hitTest(x: f64, y: f64, target?: HitTarget): Promise<HitResult[]>
144
+ /** Pin `entity` to `pose`: the host's anchor drives its matrix from now on (and keeps it true
145
+ * as the world map refines). @rejects notRunning */
146
+ anchor(entity: Entity, pose: Pose): Promise<void>
147
+ /** Stop driving `entity` (its matrix stays where it was). */
148
+ release(entity: Entity): Promise<void>
149
+ /** The latest camera frame as a JPEG (the raw image, not the composite — that is
150
+ * `Scene.capture()`). @rejects notRunning failed */
151
+ captureFrame(): Promise<File>
152
+ }
@@ -41,11 +41,11 @@ export type ARPlaneEvent = {
41
41
  mergedInto: ARPlane | null;
42
42
  };
43
43
  export type ARSceneOptions = SceneOptions & {
44
- /** "world" (default): planes, hit tests, anchors, image targets. "images": image targets only —
45
- * lighter on iOS; no planes, no hit test, no `root`. */
44
+ /** "world" (default): planes, hit tests, anchors, image and QR targets. "images": image and QR
45
+ * targets only — lighter on iOS; no planes, no hit test, no `root`. */
46
46
  mode?: ARMode;
47
47
  };
48
- export type AROpenOptions = PresentOptions & Omit<StartOptions, "mode" | "images"> & {
48
+ export type AROpenOptions = PresentOptions & Omit<StartOptions, "mode" | "images" | "codes"> & {
49
49
  /** The scene's ambient (its `environmentIntensity`) follows the estimated light; turns
50
50
  * `lightEstimation` on. */
51
51
  followLight?: boolean;
@@ -55,6 +55,13 @@ export type ARSceneEvents = {
55
55
  plane: (event: ARPlaneEvent) => void;
56
56
  light: (estimate: ARLightEstimate) => void;
57
57
  error: (error: ARError) => void;
58
+ /** The session ended on its own — the user left it (the back gesture of an immersive web session)
59
+ * or the system took it — and the scene leaves: popped when it is the router's top, else closed
60
+ * (`onClose` is where an app opens what comes next, as after `scene.close()`). Not an error. Sent
61
+ * before the scene leaves. (A `Router.pop()` of an AR scene by the app does NOT end its session:
62
+ * a covered scene comes back with it — on the web an immersive session then stays on; `close()`
63
+ * ends it.) */
64
+ end: (reason: "user" | "system") => void;
58
65
  };
59
66
  export type AnchorEvents = NodeEvents & {
60
67
  /** The anchor began tracking: found, placed, or regained after a relocalization. */
@@ -63,9 +70,10 @@ export type AnchorEvents = NodeEvents & {
63
70
  untrack: () => void;
64
71
  };
65
72
  /**
66
- * A node the AR session drives: it follows a place in the room (`scene.anchor(pose)`, `scene.root`)
67
- * or an image (`scene.createAnchor(image)`). Put content under it. `tracked` says whether the session
68
- * holds it now; `track` / `untrack` fire when that changes.
73
+ * A node the AR session drives: it follows a place in the room (`scene.anchor(pose)`, `scene.root`),
74
+ * an image (`scene.createAnchor(image)`) or a QR code (`scene.createQRAnchor(text, size)`). Put
75
+ * content under it. `tracked` says whether the session holds it now; `track` / `untrack` fire when
76
+ * that changes.
69
77
  */
70
78
  export declare class Anchor extends Node {
71
79
  /** Whether the session holds the anchor now (it moves with what it follows). */
@@ -76,16 +84,20 @@ export declare class Anchor extends Node {
76
84
  remove(): void;
77
85
  }
78
86
  /**
79
- * An anchor that follows an image (`scene.createAnchor(image, physicalWidth)`): while tracked it sits on
80
- * the image — a rigid pose in metres, no scale: its centre, Y out of the image, the image in X (its
81
- * width) and Z (towards its bottom). How big the image is, is `extent`.
87
+ * An anchor that follows an image (`scene.createAnchor(image, physicalWidth)`) or a printed QR code
88
+ * (`scene.createQRAnchor(text, size)`, `code` its text): while tracked it sits on it — a rigid pose
89
+ * in metres, no scale: its centre, Y out of it, the image in X (its width; a code's top edge) and Z
90
+ * (towards its bottom). How big it is, is `extent`.
82
91
  */
83
92
  export declare class ImageAnchor extends Anchor {
84
93
  constructor(physicalWidth: number);
85
94
  /** [width, height] in metres, along the anchor's X and Z: the session's estimate of the printed
86
95
  * image, refined while it tracks it. Before the image is first found: [physicalWidth, 0] — the
87
- * height comes with the image's proportions, which the session reads, at the first `track`. */
96
+ * height comes with the image's proportions, which the session reads, at the first `track`. A QR
97
+ * code's is [size, size] from the start (a code is square). */
88
98
  get extent(): [number, number];
99
+ /** The text of the QR code the anchor follows (`createQRAnchor`); null for an image's. */
100
+ get code(): string | null;
89
101
  }
90
102
  /** A surface the session found: a floor, a table, a wall. Its node sits at the plane's centre with Y
91
103
  * along its normal; `extent` and `polygon` are in that space. */
@@ -124,11 +136,15 @@ export declare class ARScene extends Scene {
124
136
  private _shown;
125
137
  /** The open's start while it is out (`_begin`). `closed`: close() ended its session meanwhile — the
126
138
  * half refuses a start its close cut short ("failed"), which is the close's doing: the open was
127
- * superseded, not failed. `images`: the image targets changed meanwhile, which the start did not
128
- * carry (it read them when it was sent) — the set goes to the session once it runs. */
139
+ * superseded, not failed. `images` / `codes`: the image / QR targets changed meanwhile, which the
140
+ * start did not carry (it read them when it was sent) — the set goes to the session once it runs. */
129
141
  private _startOut;
130
- /** A `setImages` is due at the end of this turn (`_setImages`). */
131
- private _imagesDue;
142
+ /** A `setImages` / `setCodes` is due at the end of this turn (`_setTargets`). */
143
+ private readonly _due;
144
+ /** The QR targets (`createQRAnchor`). */
145
+ private readonly _codes;
146
+ /** This host's half is older than the QR targets: said once. */
147
+ private _codesWarned;
132
148
  private _options;
133
149
  private _root;
134
150
  /** The first horizontal plane's pose: where `root` is pinned. */
@@ -167,6 +183,15 @@ export declare class ARScene extends Scene {
167
183
  * while not tracked. Before `open()`, or any time: on a running scene the image set is updated in
168
184
  * place — the tracking, the planes and the anchors stay. */
169
185
  createAnchor(source: FetchResponse | File, physicalWidth?: number): ImageAnchor;
186
+ /** An anchor that follows a printed QR code whose text is `text`: the session finds the symbol in
187
+ * the camera image and keeps the anchor on it, as on an image — a rigid pose in metres (its centre,
188
+ * Y out of the symbol, X along its top edge, Z towards its bottom), `anchor.extent` = [size, size].
189
+ * `size` = the printed side of the symbol in metres (its modules, no quiet zone): how far it is
190
+ * comes from it. Hidden while not tracked. Before `open()`, or any time: on a running scene the set
191
+ * of codes is updated in place. The web finds codes first (WebXR); on a host whose `ar` plugin does
192
+ * not track them the anchor is never tracked, and the console says so. `text` null or "" = ANY
193
+ * code: the first symbol decoded (a test, a plaque whose text the app does not know). */
194
+ createQRAnchor(text: string | null, size: number): ImageAnchor;
170
195
  /** An anchor at `pose` (a hit test's, say): the session keeps it where it is in the room as its
171
196
  * map of the world improves. `anchor.remove()` lets it go. */
172
197
  anchor(pose: ARPoseLike): Anchor;
@@ -202,18 +227,19 @@ export declare class ARScene extends Scene {
202
227
  private _emit;
203
228
  private _startOptions;
204
229
  private _targets;
230
+ private _codeTargets;
205
231
  private _begin;
206
232
  /** The session is over (closed, failed): every anchor untracked, the planes gone. */
207
233
  private _drop;
208
234
  private _end;
209
235
  private _lost;
210
- /** The image targets changed (one added or removed): a running session takes the whole set in place
211
- * (`setImages` — the tracking, the planes and the anchors stay). Sent at the end of the turn: several
212
- * targets added in a row are one set. While the open's start is out it waits for it (the start
213
- * carried the set it read when it was sent); before a session there is nothing to tell — the start
214
- * carries the set. Nothing waits for it: a set the half refuses is a console line, and an anchor
215
- * whose image the session did not take never tracks. */
216
- private _setImages;
236
+ /** The image targets or the QR targets changed (one added or removed): a running session takes the
237
+ * whole set in place (`setImages` / `setCodes` — the tracking, the planes and the anchors stay). Sent
238
+ * at the end of the turn: several targets added in a row are one set. While the open's start is out
239
+ * it waits for it (the start carried the set it read when it was sent); before a session there is
240
+ * nothing to tell — the start carries the set. Nothing waits for it: a set the half refuses is a
241
+ * console line, and an anchor whose target the session did not take never tracks. */
242
+ private _setTargets;
217
243
  /** A wire call to the running session; rejects without one. */
218
244
  private _wire;
219
245
  /** Pin `anchor` at `pose` (where it is until the session holds it) — now, or when a session starts. */
@@ -1,9 +1,9 @@
1
1
  import { ServiceChannel, type Entity, type EventReaders, type File } from "../../../../plugin";
2
- import type { AREvents, ARParams, HitResult, HitTarget, ImageTarget, Pose, StartOptions } from "../contract";
2
+ import type { AREvents, ARParams, HitResult, HitTarget, ImageTarget, Pose, QRTarget, StartOptions } from "../contract";
3
3
  /** What the halves register under. */
4
4
  export declare const NAME = "ar";
5
5
  /** The contract's version: the highest `@since` of its members. */
6
- export declare const VERSION = 1;
6
+ export declare const VERSION = 2;
7
7
  /** The events whose payload holds a File: read before a listener sees it. */
8
8
  export declare const READERS: EventReaders;
9
9
  /** Start tracking; resolves at the first frame. A second start rejects "busy".
@@ -19,6 +19,13 @@ export declare const stop: (service: ServiceChannel<AREvents>) => Promise<void>;
19
19
  * `StartOptions.images`.
20
20
  * Rejects with "notRunning" / "failed". */
21
21
  export declare const setImages: (service: ServiceChannel<AREvents>, images: ImageTarget[]) => Promise<void>;
22
+ /** The twin of `setImages` for QR codes: replace the set of QR targets of a RUNNING session
23
+ * without restarting it; a code no longer in the set stops driving its entity (`tracked: false`
24
+ * once if it was tracked). Before `start`, the codes are `StartOptions.codes`. A half that tracks
25
+ * no codes yet (the native ones) rejects "failed".
26
+ * Contract version 2: an older half rejects the call — ask `supports("setCodes")`.
27
+ * Rejects with "notRunning" / "failed". */
28
+ export declare const setCodes: (service: ServiceChannel<AREvents>, codes: QRTarget[]) => Promise<void>;
22
29
  /** What is under a point of the scene's view — in its touches' space (a scene touch's `clientX` /
23
30
  * `clientY`: the units of the gl facade's viewport) — nearest first. Empty when nothing is, or on
24
31
  * a host without surfaces (the web).
@@ -35,7 +42,7 @@ export declare const release: (service: ServiceChannel<AREvents>, entity: Entity
35
42
  * Rejects with "notRunning" / "failed". */
36
43
  export declare const captureFrame: (service: ServiceChannel<AREvents>) => Promise<File>;
37
44
  /** A method of the contract. */
38
- export type Method = "start" | "stop" | "setImages" | "hitTest" | "anchor" | "release" | "captureFrame";
45
+ export type Method = "start" | "stop" | "setImages" | "setCodes" | "hitTest" | "anchor" | "release" | "captureFrame";
39
46
  /** Whether this host has the service "ar" — and, given a method, a half new enough for it
40
47
  * (a method added after the first version of the contract is tagged `@since`). */
41
48
  export declare const supports: (method?: Method) => boolean;
@@ -55,6 +62,13 @@ export declare class ARBase extends ServiceChannel<AREvents> {
55
62
  * `StartOptions.images`.
56
63
  * Rejects with "notRunning" / "failed". */
57
64
  setImages(images: ImageTarget[]): Promise<void>;
65
+ /** The twin of `setImages` for QR codes: replace the set of QR targets of a RUNNING session
66
+ * without restarting it; a code no longer in the set stops driving its entity (`tracked: false`
67
+ * once if it was tracked). Before `start`, the codes are `StartOptions.codes`. A half that tracks
68
+ * no codes yet (the native ones) rejects "failed".
69
+ * Contract version 2: an older half rejects the call — ask `supports("setCodes")`.
70
+ * Rejects with "notRunning" / "failed". */
71
+ setCodes(codes: QRTarget[]): Promise<void>;
58
72
  /** What is under a point of the scene's view — in its touches' space (a scene touch's `clientX` /
59
73
  * `clientY`: the units of the gl facade's viewport) — nearest first. Empty when nothing is, or on
60
74
  * a host without surfaces (the web).
@@ -0,0 +1,64 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/audio-recorder/contract.d.ts — do not edit here.
2
+ // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
+ // audio-recorder — audio recording to a file: voice messages, voice notes.
4
+ // A session is ONE recording (the app-facing `AudioRecorder` opens one per `start()`): start, pause
5
+ // / resume, then `stop()` for the file or `cancel()` to discard it. The microphone permission is the
6
+ // contract's (`@permission microphone` on start): the host asks, the halves never do.
7
+ //
8
+ // The format is the platform's encoder: AAC in an .m4a on iOS and Android, what the browser's
9
+ // MediaRecorder gives on the web (webm/opus in Chrome and Firefox, mp4 in Safari) — `mimeType`
10
+ // says which, the app's server decides whether to transcode.
11
+
12
+ import type { File, Service, f64 } from "../../../plugin"
13
+
14
+ export interface RecordOptions {
15
+ /** Kilobits per second of the encoded audio. Default 64 — speech; 128 for music. */
16
+ bitrate?: f64
17
+ /** Samples per second. Default 44100; the browser keeps its own. */
18
+ sampleRate?: f64
19
+ /** Stop on its own after this many ms: `ended` fires with "maxDuration", `stop()` then returns
20
+ * the recording. Default: no limit. */
21
+ maxDuration?: f64
22
+ }
23
+
24
+ export type EndReason = "maxDuration" | "interrupted" | "failed"
25
+
26
+ export interface Recording {
27
+ /** The audio file — `recording.m4a` / `.webm` / `.mp4` by the encoder. */
28
+ file: File
29
+ /** "audio/mp4", "audio/webm", … — the container, without codec parameters. */
30
+ mimeType: string
31
+ /** Seconds of audio (the pauses left out). */
32
+ duration: f64
33
+ }
34
+
35
+ export interface AudioRecorderParams {}
36
+
37
+ export interface AudioRecorderEvents {
38
+ /** The input level, 0..1, about ten times a second while recording (not while paused): a meter,
39
+ * a waveform. */
40
+ level: { value: f64 }
41
+ /** The recording stopped on its own: the limit (`maxDuration`), an interruption (a call came in),
42
+ * a failure of the encoder. What was recorded is kept until `stop()` (which returns it) or
43
+ * `cancel()`. */
44
+ ended: { reason: EndReason }
45
+ }
46
+
47
+ export interface AudioRecorder extends Service<"audioRecorder", AudioRecorderParams, AudioRecorderEvents> {
48
+ /** Start recording. One recording per session: a second start rejects "busy".
49
+ * @permission microphone
50
+ * @rejects busy failed */
51
+ start(options?: RecordOptions): Promise<void>
52
+ /** Pause: the level stops, the duration does not grow. Rejects "notRecording" before a start
53
+ * or after the recording ended.
54
+ * @rejects notRecording failed */
55
+ pause(): Promise<void>
56
+ /** @rejects notRecording failed */
57
+ resume(): Promise<void>
58
+ /** Stop and get the recording (also after `ended`: the file was kept). Rejects "failed" when
59
+ * nothing could be encoded.
60
+ * @rejects notRecording failed */
61
+ stop(): Promise<Recording>
62
+ /** Stop and discard. Nothing to discard is not an error. */
63
+ cancel(): Promise<void>
64
+ }