@nayan-ui/engine 1.0.0-beta.4 → 1.0.0-beta.6

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 (77) hide show
  1. package/README.md +225 -116
  2. package/lib/module/assets/ProceduralCache.js +5 -0
  3. package/lib/module/assets/loft.js +15 -63
  4. package/lib/module/assets/procedural.js +500 -0
  5. package/lib/module/assets/shape.js +147 -245
  6. package/lib/module/assets/texture.js +65 -0
  7. package/lib/module/drawing/DrawingDocument.js +1 -1
  8. package/lib/module/index.js +4 -1
  9. package/lib/module/media/audio-worklet.js +27 -0
  10. package/lib/module/media/audio.js +20 -0
  11. package/lib/module/media/input.web.js +107 -0
  12. package/lib/module/media/microphone.js +89 -0
  13. package/lib/module/media/speech.js +80 -0
  14. package/lib/module/media/speechPlatform.js +12 -0
  15. package/lib/module/media/speechPlatform.web.js +16 -0
  16. package/lib/module/native/BrowserEngine.js +122 -0
  17. package/lib/module/native/NativeNayanEngine.web.js +2 -0
  18. package/lib/module/render/GameView.js +45 -13
  19. package/lib/module/render/quality.js +151 -17
  20. package/lib/module/world/World.js +63 -0
  21. package/lib/typescript/src/assets/ProceduralCache.d.ts +2 -0
  22. package/lib/typescript/src/assets/loft.d.ts +3 -1
  23. package/lib/typescript/src/assets/procedural.d.ts +394 -0
  24. package/lib/typescript/src/assets/shape.d.ts +13 -7
  25. package/lib/typescript/src/assets/texture.d.ts +63 -0
  26. package/lib/typescript/src/index.d.ts +8 -2
  27. package/lib/typescript/src/media/audio.d.ts +13 -0
  28. package/lib/typescript/src/media/input.web.d.ts +4 -0
  29. package/lib/typescript/src/media/microphone.d.ts +51 -0
  30. package/lib/typescript/src/media/speech.d.ts +38 -0
  31. package/lib/typescript/src/media/speechPlatform.d.ts +5 -0
  32. package/lib/typescript/src/media/speechPlatform.web.d.ts +5 -0
  33. package/lib/typescript/src/native/EngineBridge.d.ts +7 -1
  34. package/lib/typescript/src/native/spec/NativeNayanEngine.d.ts +72 -0
  35. package/lib/typescript/src/render/GameView.d.ts +30 -3
  36. package/lib/typescript/src/render/quality.d.ts +54 -2
  37. package/lib/typescript/src/types.d.ts +9 -2
  38. package/lib/typescript/src/world/World.d.ts +16 -0
  39. package/lib/typescript/src/world/types.d.ts +8 -0
  40. package/lib/web/audio-worklet.js +27 -0
  41. package/lib/web/engine_core_bg.wasm +0 -0
  42. package/lib/web/index.js +1181 -422
  43. package/lib/web/types/src/assets/ProceduralCache.d.ts +2 -0
  44. package/lib/web/types/src/assets/loft.d.ts +3 -1
  45. package/lib/web/types/src/assets/procedural.d.ts +393 -0
  46. package/lib/web/types/src/assets/shape.d.ts +13 -7
  47. package/lib/web/types/src/assets/texture.d.ts +63 -0
  48. package/lib/web/types/src/index.d.ts +8 -2
  49. package/lib/web/types/src/media/audio.d.ts +13 -0
  50. package/lib/web/types/src/media/input.d.ts +3 -0
  51. package/lib/web/types/src/media/microphone.d.ts +50 -0
  52. package/lib/web/types/src/media/speech.d.ts +37 -0
  53. package/lib/web/types/src/media/speechPlatform.d.ts +4 -0
  54. package/lib/web/types/src/native/EngineBridge.d.ts +7 -1
  55. package/lib/web/types/src/native/spec/NativeNayanEngine.d.ts +72 -0
  56. package/lib/web/types/src/render/GameView.d.ts +30 -3
  57. package/lib/web/types/src/render/quality.d.ts +54 -2
  58. package/lib/web/types/src/types.d.ts +9 -2
  59. package/lib/web/types/src/world/World.d.ts +16 -0
  60. package/lib/web/types/src/world/types.d.ts +8 -0
  61. package/native/android/src/main/AndroidManifest.xml +10 -0
  62. package/native/android/src/main/java/com/nayanui/engine/EngineMicrophone.java +60 -0
  63. package/native/android/src/main/java/com/nayanui/engine/EngineSpeech.java +28 -0
  64. package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceView.java +1 -0
  65. package/native/android/src/main/java/com/nayanui/engine/NayanEnginePackage.java +2 -0
  66. package/native/core/build/EngineCore.xcframework/ios-arm64/libengine_core.a +0 -0
  67. package/native/core/build/EngineCore.xcframework/ios-arm64-simulator/libengine_core.a +0 -0
  68. package/native/core/build/android/arm64-v8a/libengine_core.a +0 -0
  69. package/native/core/build/android/armeabi-v7a/libengine_core.a +0 -0
  70. package/native/core/build/android/x86/libengine_core.a +0 -0
  71. package/native/core/build/android/x86_64/libengine_core.a +0 -0
  72. package/native/core/include/engine_core.h +63 -1
  73. package/native/cpp/NayanEngineModule.cpp +296 -0
  74. package/native/cpp/NayanEngineModule.h +40 -0
  75. package/native/cpp/generated/NayanEngineSpecJSI.h +397 -0
  76. package/package.json +1 -1
  77. package/src/native/spec/NativeNayanEngine.ts +77 -0
@@ -0,0 +1,5 @@
1
+ export declare const speechPlatform: {
2
+ readonly supported: boolean;
3
+ request(command: string): Promise<string>;
4
+ };
5
+ //# sourceMappingURL=speechPlatform.web.d.ts.map
@@ -10,15 +10,21 @@ type BufferMethod<T> = T extends (...args: infer A) => infer R ? (...args: {
10
10
  }) => BufferValue<R, ArrayBuffer> : T;
11
11
  /** Native-only (a display clock and a render thread): absent on the web core and on older app builds. */
12
12
  type NativeFrames = 'publishFrame' | 'renderStats' | 'hasFrameClock' | 'startFrames' | 'stopFrames';
13
- type BinarySpec = Omit<Spec, 'geometrySolid' | 'geometryOptimize' | 'modelSize' | 'textureSize' | NativeFrames>;
13
+ type BinarySpec = Omit<Spec, 'speechRequest' | 'geometrySolid' | 'geometryOptimize' | 'modelSize' | 'modelBake' | 'textureSize' | 'worldgenTerrain' | 'worldgenMesh' | 'worldgenTilt' | 'worldgenMove' | NativeFrames>;
14
14
  /** The same runtime ABI on JSI and WASM, with precise TypeScript buffer and tuple contracts. */
15
15
  export type EngineBridge = {
16
16
  [K in keyof BinarySpec]: BufferMethod<BinarySpec[K]>;
17
17
  } & {
18
+ speechRequest?: Spec['speechRequest'];
18
19
  geometrySolid(recipe: string): NativeGeometry;
19
20
  geometryOptimize(vertices: ArrayBufferLike, indices: ArrayBufferLike, ratio: number, error: number, lockBorders: boolean): NativeGeometry;
20
21
  modelSize(mesh: number): [x: number, y: number, z: number];
22
+ modelBake(mesh: number, spec: string): [] | [texture: number, x: number, y: number, extent: number];
21
23
  textureSize(texture: number): [width: number, height: number];
24
+ worldgenTerrain(world: number, material: number): NativeGeometry;
25
+ worldgenMesh(recipe: string): NativeGeometry;
26
+ worldgenTilt(world: number, x: number, z: number, yaw: number): [x: number, y: number, z: number, w: number];
27
+ worldgenMove(world: number, x: number, z: number, dx: number, dz: number, radius: number, bodies: ArrayBufferLike): [x: number, z: number];
22
28
  } & {
23
29
  [K in NativeFrames]?: BufferMethod<Spec[K]>;
24
30
  };
@@ -9,6 +9,8 @@ import { type TurboModule } from 'react-native';
9
9
  * and stay valid until `destroyWorld`.
10
10
  */
11
11
  export interface Spec extends TurboModule {
12
+ /** System speech commands; JSON envelope contains value or error. */
13
+ speechRequest(command: string): Promise<string>;
12
14
  createWorld(capacity: number): number;
13
15
  destroyWorld(world: number): void;
14
16
  /** Returns the entity id, or -1 if the world is full or the description is rejected. */
@@ -98,6 +100,10 @@ export interface Spec extends TurboModule {
98
100
  modelIndices(mesh: number): Object;
99
101
  /** Full bounding-box size [x, y, z]. */
100
102
  modelSize(mesh: number): Array<number>;
103
+ /** Bakes a model into a new texture (spec: BakeSpec JSON): [texture, center x, center y, extent], or [] on failure (`loadError`). */
104
+ modelBake(mesh: number, spec: string): Array<number>;
105
+ /** Whether a baked picture is kept under `key`. */
106
+ bakeKept(key: string): boolean;
101
107
  /** The model's base color texture id, or -1. */
102
108
  modelTexture(mesh: number): number;
103
109
  /** A new mesh id with `base`'s geometry (to give it another texture), or -1. */
@@ -185,6 +191,12 @@ export interface Spec extends TurboModule {
185
191
  * scene. count 0 removes it.
186
192
  */
187
193
  renderOverlay(surface: number, matrices: Object, colors: Object, regions: Object, ranges: Object, count: number, width: number, height: number): void;
194
+ /**
195
+ * Static scenery behind the scene (the world's instance buffers, as `renderOverlay`): drawn into a
196
+ * cached texture with each frame's camera and light, and again only when this is called again or
197
+ * the view, light, size or quality change. count 0 removes it.
198
+ */
199
+ renderBackdrop(surface: number, matrices: Object, colors: Object, regions: Object, ranges: Object, count: number): void;
188
200
  renderError(): string;
189
201
  /** "vulkan", "gl" or "metal" ("" before the first surface). */
190
202
  renderBackend(): string;
@@ -202,9 +214,69 @@ export interface Spec extends TurboModule {
202
214
  audioSetVolume(volume: number): void;
203
215
  audioSetMuted(muted: boolean): void;
204
216
  audioIsRunning(): boolean;
217
+ /** Frees a sound's samples (a played recording); its id then plays nothing. */
218
+ audioRelease(sound: number): void;
219
+ /** A sound's length in seconds (0 if unknown or released). */
220
+ audioDuration(sound: number): number;
221
+ /** Loudness per `1 / perSecond` s of a sound, as a Float32 ArrayBuffer. */
222
+ audioLevels(sound: number, perSecond: number): Object;
223
+ /** 1 granted, 0 not asked yet (or not granted on Android), -1 denied. */
224
+ micPermission(): number;
225
+ /** Shows the system prompt the first time; resolves to whether recording is allowed. */
226
+ micRequestPermission(): Promise<boolean>;
227
+ /** Starts listening, keeping the last `maxSeconds` of input; true if it's opening. */
228
+ micStart(maxSeconds: number): boolean;
229
+ micStop(): void;
230
+ /** 0 off, 1 opening, 2 listening, -1 failed. */
231
+ micState(): number;
232
+ /** Input loudness now, 0..1. */
233
+ micLevel(): number;
234
+ /** Seconds of input held. */
235
+ micHeld(): number;
236
+ /** A sound id of the `seconds` of input that ended `skipEnd` seconds ago, or -1; forgets what was heard. */
237
+ micTake(seconds: number, skipEnd: number): number;
205
238
  hapticsSupported(): boolean;
206
239
  /** Taps as flat [time, intensity, sharpness, ...] triples. */
207
240
  hapticsPlay(taps: Array<number>, throttle: boolean): void;
241
+ /** Builds (or, with a `key` saved from the same recipe, restores) a world. 0 on failure (`worldgenError`). */
242
+ worldgenBuild(recipe: string, key: string, prepareTerrain: boolean): number;
243
+ /** `worldgenBuild` on a background native thread. */
244
+ worldgenBuildAsync(recipe: string, key: string, prepareTerrain: boolean): Promise<number>;
245
+ worldgenError(): string;
246
+ /** Whether a world was saved under `key`. */
247
+ worldgenKept(key: string): boolean;
248
+ worldgenFree(world: number): void;
249
+ /** Spawn, restored flag, layer sizes and picked spots as JSON. */
250
+ worldgenInfo(world: number): string;
251
+ /** A scatter layer's items (f64, stride from `worldgenInfo`). */
252
+ worldgenLayer(world: number, name: string): Object;
253
+ /** Reachable cells as f64 [x, z] pairs. */
254
+ worldgenReachable(world: number): Object;
255
+ /** Overview surface codes (u8, size × size). */
256
+ worldgenOverview(world: number): Object;
257
+ /** Re-picks the spots for `run`; false keeps the previous ones. */
258
+ worldgenPick(world: number, run: number): boolean;
259
+ /** The terrain mesh of a material, handed over once: { vertices, indices }. */
260
+ worldgenTerrain(world: number, material: number): Object;
261
+ /** Builds a procedural mesh recipe: { vertices, indices }. */
262
+ worldgenMesh(recipe: string): Object;
263
+ /** Recolors 11-float vertices in place by a field function (`{ fields, out }`) of each surface point. */
264
+ worldgenPaint(vertices: Object, recipe: string): boolean;
265
+ worldgenGround(world: number, x: number, z: number): number;
266
+ worldgenSurface(world: number, x: number, z: number): number;
267
+ /** The platform under a point, or -1. */
268
+ worldgenPlatform(world: number, x: number, z: number): number;
269
+ worldgenWalkable(world: number, x: number, z: number, radius: number): boolean;
270
+ worldgenClearance(world: number, x: number, z: number, radius: number): number;
271
+ worldgenSolidClearance(world: number, x: number, z: number, radius: number, bottom: number, top: number): number;
272
+ /** A named field's index for `worldgenField`, or -1. */
273
+ worldgenFieldIndex(world: number, name: string): number;
274
+ worldgenField(world: number, field: number, x: number, z: number): number;
275
+ /** Ground tilt as a quaternion [x, y, z, w]. */
276
+ worldgenTilt(world: number, x: number, z: number, yaw: number): Array<number>;
277
+ worldgenSight(world: number, ax: number, az: number, bx: number, bz: number, radius: number, step: number, rise: number): boolean;
278
+ /** Swept move-and-slide; `bodies` is an f64 buffer of [x, z, radius] triples. Returns [x, z]. */
279
+ worldgenMove(world: number, x: number, z: number, dx: number, dz: number, radius: number, bodies: Object): Array<number>;
208
280
  }
209
281
  declare const _default: Spec | null | undefined;
210
282
  export default _default;
@@ -15,12 +15,19 @@ import { type RenderQuality, type RenderQualitySetting } from './quality.js';
15
15
  export declare function afterNextFrame(fn: () => void): () => void;
16
16
  export type GameViewStats = {
17
17
  fps: number;
18
- /** Mean time spent in `onUpdate` per frame, in milliseconds. */
18
+ /** Mean time spent in `onUpdate` and stepping the worlds per frame, in milliseconds. */
19
19
  updateMs: number;
20
+ /**
21
+ * Mean JS-thread time per frame spent handing the frame and overlay to the renderer, in
22
+ * milliseconds. With a render thread this is copying only; otherwise it includes drawing.
23
+ */
24
+ publishMs: number;
20
25
  /** Average milliseconds per frame spent drawing (CPU side, including waiting on the GPU). */
21
26
  renderMs: number;
22
27
  /** The quality level drawn with (with `quality="auto"`, the one it settled on so far). */
23
28
  quality: RenderQuality;
29
+ /** The 3D scene's resolution per axis relative to the screen's: 1, or less under a pixel budget. */
30
+ sceneScale: number;
24
31
  /** The graphics API drawing: "vulkan", "gl" (OpenGL ES) or "metal". */
25
32
  backend: string;
26
33
  };
@@ -65,11 +72,22 @@ export type GameViewProps = {
65
72
  /** Distance fog density towards `background` (e.g. 0.02). Default 0 (off). */
66
73
  fog?: number;
67
74
  /**
68
- * Resolution, anti-aliasing and shadow quality. "auto" (default) starts at "high", steps down only
69
- * while the GPU really can't keep up (shadow detail first, then anti-aliasing, resolution last) and
75
+ * Resolution, anti-aliasing and shadows. "auto" (default) starts at "high", steps down only while
76
+ * the GPU really can't keep up (crisper shadows, then no shadows or anti-aliasing, resolution last) and
70
77
  * steps back up once frames are smooth again, so every phone gets the best look it can hold.
71
78
  */
72
79
  quality?: RenderQualitySetting;
80
+ /**
81
+ * Remembers the level "auto" settles on under this key (e.g. the game's id) in the engine's durable
82
+ * store, so every later view with the same key, after app restarts too, starts there rather than at "high".
83
+ */
84
+ qualityKey?: string;
85
+ /**
86
+ * Whether "auto" judges the device by the frames now (default true). Pass false while what's on
87
+ * screen isn't the game being played (a results card, a menu, a ready screen): their cost says
88
+ * nothing about the game, and a level chosen there would be remembered for the next visit.
89
+ */
90
+ adaptQuality?: boolean;
73
91
  /** Suspend updates and drawing while keeping the native surface mounted. */
74
92
  paused?: boolean;
75
93
  style?: ViewStyle;
@@ -92,6 +110,15 @@ export type GameViewProps = {
92
110
  * frame like `source`.
93
111
  */
94
112
  overlay?: RenderSource;
113
+ /**
114
+ * Static scenery behind `source` (a meadow, sky and hills; a board's backdrop): the renderer draws it
115
+ * once into a cached image with this view's camera and light, and draws it again only when it
116
+ * changes or the camera, light, size or quality do. Each frame then costs one full-screen image
117
+ * instead of the whole scenery, and `source` is always drawn in front of it. It isn't stepped (its
118
+ * velocities and animations stand still): call `backdrop.update(0)` after spawning or changing it.
119
+ * Defaults to `source.backdrop`: a World's still entities (`still: true`, `backdropLayer`).
120
+ */
121
+ backdrop?: RenderSource;
95
122
  /**
96
123
  * Touches for the overlay, in points in the view. Return true from "start" to claim the touch (a
97
124
  * button was hit): its moves and end come here, and the game gets no drag, tap or swipe for it.
@@ -6,14 +6,53 @@ export type QualityLevel = {
6
6
  maxPixelRatio: number;
7
7
  /** MSAA samples: 4 or 1 (off). */
8
8
  samples: 1 | 4;
9
+ /**
10
+ * Smooths edges after drawing (FXAA), in the pass that stretches a reduced scene to the screen (so
11
+ * only with `maxPixels`): there it costs a few texture reads per pixel. At full resolution it would
12
+ * need that whole extra pass, which costs a tile-based phone GPU more than 4x MSAA. The overlay (HUD
13
+ * text) is drawn after it, untouched.
14
+ */
15
+ fxaa?: boolean;
16
+ /** Whether shadows are drawn at all (a scene's light may still turn them off). */
17
+ shadows: boolean;
9
18
  /** Shadow map width and height in texels. */
10
19
  shadowSize: number;
11
20
  /** 3x3 filtered (soft) shadow edges, or a single tap (hard). */
12
21
  softShadows: boolean;
22
+ /**
23
+ * Most device pixels drawn per frame, whatever the screen's ratio: a cap on the pixel ratio alone
24
+ * leaves a low-density tablet (1.75x on 1200x2000) drawing its full resolution even at "lowest".
25
+ */
26
+ maxPixels?: number;
13
27
  };
14
28
  /** From best looking to cheapest; "auto" walks down this list only as far as it must. */
15
29
  export declare const QUALITIES: readonly RenderQuality[];
16
30
  export declare const QUALITY_LEVELS: Record<RenderQuality, QualityLevel>;
31
+ /**
32
+ * What "auto" walks through, from best to cheapest: a level, and the most pixels its 3D scene is drawn
33
+ * with. Before giving up an effect it draws the scene a little smaller, as native games scale their
34
+ * resolution: stretched over the screen under the full-resolution HUD, 1.5 MP (0.8x a 1200x2000
35
+ * tablet) can't be told from the screen's own resolution and 1.1 MP barely, while every effect stays.
36
+ * On the Adreno 610 tablet that took Crossy Road from 47 to 60 fps and Whack a Mole from 53 to 59 at
37
+ * "high": a tile-based GPU's cost under 4x MSAA grows with pixels times the triangles in each tile.
38
+ */
39
+ export type QualityStep = {
40
+ quality: RenderQuality;
41
+ maxPixels?: number;
42
+ };
43
+ export declare const AUTO_STEPS: readonly QualityStep[];
44
+ /** A step's level: its quality's, with the step's pixel budget when that is smaller. */
45
+ export declare function stepLevel(step: QualityStep): QualityLevel;
46
+ /** The pixel ratio a level draws a `width` x `height` point view at, on a screen of ratio `screen`. */
47
+ export declare function levelPixelRatio(level: QualityLevel, screen: number, width: number, height: number): number;
48
+ /** Durable records (the engine's Rust store); absent on builds without it, which then remember per session. */
49
+ export type QualityStore = {
50
+ cacheRead?: (key: string) => ArrayBuffer;
51
+ cacheWrite?: (key: string, bytes: ArrayBuffer) => boolean;
52
+ };
53
+ export declare function settledQuality(key: string | undefined, store?: QualityStore): number | undefined;
54
+ /** Records the step for `key`, writing it to `store` only when it changes. */
55
+ export declare function rememberQuality(key: string | undefined, step: number, store?: QualityStore): void;
17
56
  /** One second of frames, as the frame loop measured it. */
18
57
  export type FrameReport = {
19
58
  /** The typical (median) time between frames, in ms: hiccups (garbage collection, a busy JS frame) don't move it. */
@@ -22,10 +61,11 @@ export type FrameReport = {
22
61
  renderMs: number;
23
62
  };
24
63
  /**
25
- * Picks the level for "auto": starts at the best, steps down one level after `SLOW_SECONDS`
64
+ * Picks the step (of AUTO_STEPS) for "auto": starts at the best, steps down one after `SLOW_SECONDS`
26
65
  * consecutive seconds whose typical frame misses ~50 fps while drawing took most of it (after a
27
66
  * single second when it misses ~33 fps, so a struggling phone recovers in seconds rather than
28
- * stuttering through every level), and steps back up after `FAST_SECONDS` smooth seconds. A level that fails again soon after being restored
67
+ * stuttering through every level), and steps back up after `FAST_SECONDS` smooth seconds (`HEADROOM_SECONDS` when
68
+ * the GPU has plenty of time to spare). A level that fails again soon after being restored
29
69
  * becomes the ceiling, so it never bounces.
30
70
  */
31
71
  export declare class AutoQuality {
@@ -38,9 +78,21 @@ export declare class AutoQuality {
38
78
  private slow;
39
79
  private verySlow;
40
80
  private fast;
81
+ private headroom;
82
+ /** `start`: the step to begin at (one remembered from an earlier visit), or a quality's first step. */
83
+ constructor(start?: number | RenderQuality);
84
+ /** The current step's index in AUTO_STEPS. */
85
+ get step(): number;
41
86
  get quality(): RenderQuality;
87
+ /** What to draw with: the step's level and pixel budget. */
88
+ get level(): QualityLevel;
42
89
  /** Feeds one second of frames; returns true when the level changed. */
43
90
  report(now: number, frames: FrameReport): boolean;
91
+ /**
92
+ * Frames that don't count (a results card over the game): forgets the streaks and, once frames
93
+ * count again, waits `SETTLE_MS` before judging, as after a change of level.
94
+ */
95
+ hold(now: number): void;
44
96
  private moved;
45
97
  }
46
98
  /** The middle value of `values` (sorted in place). */
@@ -7,6 +7,8 @@ export declare const MAX_MESHES = 4096;
7
7
  export interface RenderSource {
8
8
  /** Native world id, when this source supports skeletal characters. */
9
9
  readonly nativeWorld?: number;
10
+ /** Still scenery drawn once into a cached image behind this source (a World's still entities). */
11
+ readonly backdrop?: RenderSource | null;
10
12
  /** Column-major mat4 per instance (16 floats), grouped by mesh. Only the first `count` are valid. */
11
13
  readonly matrices: Float32Array;
12
14
  /** RGBA per instance (4 floats), same order as `matrices`. Alpha below 1 draws see-through. */
@@ -67,8 +69,13 @@ export type Light = {
67
69
  direction: [number, number, number];
68
70
  /** 0..1 ambient term. */
69
71
  ambient: number;
70
- /** Cast shadows (one directional shadow map centred on the camera target). Default true. */
71
- shadows?: boolean;
72
+ /**
73
+ * Cast shadows (one directional shadow map centred on the camera target). Default true.
74
+ * `'backdrop'`: only the cached backdrop layer draws its shadow map (and its own shadows); live
75
+ * entities still receive those shadows but cast none, so a frame skips the shadow pass. For scenes
76
+ * whose ground is all backdrop, where live shadows would land on nothing that shows them.
77
+ */
78
+ shadows?: boolean | 'backdrop';
72
79
  /** Half-size in world units of the shadowed area around the camera target. Default 30. */
73
80
  shadowExtent?: number;
74
81
  /**
@@ -45,6 +45,10 @@ export declare class World implements RenderSource {
45
45
  private boundsValue;
46
46
  private listenerValue;
47
47
  private updates;
48
+ /** The layer still entities live in, the stand-in each one has here, and whether one changed. */
49
+ private backdropValue;
50
+ private readonly stills;
51
+ private stillChanged;
48
52
  constructor(capacity: number);
49
53
  private get native();
50
54
  /** True once `dispose()` has run. Cleanup calls (despawn, set, has, queries, stopping animations
@@ -55,6 +59,18 @@ export declare class World implements RenderSource {
55
59
  get count(): number;
56
60
  /** Adds an entity (one native call; text adds one per letter). Throws if the world is full or an option is invalid. */
57
61
  spawn(options?: EntityOptions): Entity;
62
+ /**
63
+ * The still entities' layer (see `EntityOptions.still`), drawn once into a cached image behind this
64
+ * world; null until it's first used. GameView draws it; it's disposed with this world.
65
+ */
66
+ get backdrop(): World | null;
67
+ /**
68
+ * The still layer itself, made on first use with room for `capacity` entities: for a whole still
69
+ * scene (a meadow) spawned straight into it. Step it (`update(0)`) after changing it, which is
70
+ * what tells GameView to draw it again; it isn't stepped otherwise, so nothing in it moves.
71
+ */
72
+ backdropLayer(capacity?: number): World;
73
+ private spawnStill;
58
74
  /**
59
75
  * Changes an entity (one native call). Only the options you pass change; `null` removes.
60
76
  * Returns false if the entity is gone, the world is disposed, or an option was rejected (the others still apply).
@@ -138,6 +138,14 @@ export type EntityOptions = {
138
138
  impact?: ImpactFeedback | null;
139
139
  /** Whether `pick` can hit it. Default true. */
140
140
  pickable?: boolean;
141
+ /**
142
+ * Scenery that never moves (a board's background, a sky): drawn with the world's other still
143
+ * entities into one cached image behind everything else (`World.backdrop`), redrawn only when one
144
+ * of them changes, the camera moves or the light changes, instead of every frame. Spawn-only. The
145
+ * returned entity takes `set` and `despawn` (and goes with the world); it can't be attached, have
146
+ * physics or text, be animated or be picked, and everything live draws in front of it.
147
+ */
148
+ still?: boolean;
141
149
  };
142
150
  export type Easing = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'back' | 'bounce' | 'elastic';
143
151
  /** What `animate` changes. Anything left out stays as it is (and keeps any animation it already has). */
@@ -27,3 +27,30 @@ class NayanMixer extends AudioWorkletProcessor {
27
27
  }
28
28
  }
29
29
  registerProcessor('nayan-mixer', NayanMixer);
30
+
31
+ // The page's microphone: mixes each input block to mono and posts ~43 ms batches to the main thread,
32
+ // which hands them to the core's capture buffer.
33
+ class NayanMicrophone extends AudioWorkletProcessor {
34
+ constructor() {
35
+ super();
36
+ this.batch = new Float32Array(2048);
37
+ this.filled = 0;
38
+ }
39
+ process(inputs) {
40
+ const channels = inputs[0];
41
+ if (!channels || !channels.length) return true;
42
+ const frames = channels[0].length;
43
+ for (let i = 0; i < frames; i++) {
44
+ let sum = 0;
45
+ for (let c = 0; c < channels.length; c++) sum += channels[c][i];
46
+ this.batch[this.filled++] = sum / channels.length;
47
+ if (this.filled === this.batch.length) {
48
+ this.port.postMessage(this.batch, [this.batch.buffer]);
49
+ this.batch = new Float32Array(2048);
50
+ this.filled = 0;
51
+ }
52
+ }
53
+ return true;
54
+ }
55
+ }
56
+ registerProcessor('nayan-microphone', NayanMicrophone);
Binary file