lecodes-sdk 0.20.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/global.d.ts +48 -5
  2. package/dist/inject.js +361 -260
  3. package/dist/types/audio/Bus.d.ts +45 -0
  4. package/dist/types/audio/Sound.d.ts +28 -0
  5. package/dist/types/audio/Voice.d.ts +27 -0
  6. package/dist/types/audio/audio.d.ts +83 -0
  7. package/dist/types/audio/support.d.ts +1 -0
  8. package/dist/types/canvas/Canvas.d.ts +2 -0
  9. package/dist/types/gl/DecalSet.d.ts +148 -0
  10. package/dist/types/gl/Geometry.d.ts +17 -0
  11. package/dist/types/gl/Light.d.ts +7 -0
  12. package/dist/types/gl/Lightmap.d.ts +9 -0
  13. package/dist/types/gl/Material.d.ts +90 -2
  14. package/dist/types/gl/Mesh.d.ts +18 -1
  15. package/dist/types/gl/Model.d.ts +34 -0
  16. package/dist/types/gl/Particles.d.ts +13 -0
  17. package/dist/types/gl/Scene.d.ts +23 -0
  18. package/dist/types/gl/Texture.d.ts +29 -1
  19. package/dist/types/gl/animation/AnimationClip.d.ts +25 -12
  20. package/dist/types/gl/animation/DynamicBone.d.ts +173 -0
  21. package/dist/types/gl/{IK.d.ts → animation/IK.d.ts} +4 -4
  22. package/dist/types/gl/{Locomotion.d.ts → animation/Locomotion.d.ts} +6 -6
  23. package/dist/types/gl/animation/core.d.ts +15 -10
  24. package/dist/types/gl/audio/AudioSource.d.ts +60 -0
  25. package/dist/types/gl/audio/AudioZone.d.ts +32 -0
  26. package/dist/types/gl/audio/SceneAudio.d.ts +11 -0
  27. package/dist/types/gl/{NavAgent.d.ts → nav/NavAgent.d.ts} +4 -4
  28. package/dist/types/gl/{NavMesh.d.ts → nav/NavMesh.d.ts} +4 -4
  29. package/dist/types/gl/{CharacterController.d.ts → physics/CharacterController.d.ts} +5 -5
  30. package/dist/types/gl/{Physics.d.ts → physics/Physics.d.ts} +6 -5
  31. package/dist/types/gl/{Ragdoll.d.ts → physics/Ragdoll.d.ts} +4 -4
  32. package/dist/types/gl/{Shape.d.ts → physics/Shape.d.ts} +3 -3
  33. package/dist/types/gl/{Trigger.d.ts → physics/Trigger.d.ts} +2 -2
  34. package/dist/types/gl/state.d.ts +0 -1
  35. package/dist/types/gl/{Terrain.d.ts → terrain/Terrain.d.ts} +6 -6
  36. package/dist/types/gl/{terrainMesh.d.ts → terrain/terrainMesh.d.ts} +1 -1
  37. package/dist/types/gl/vehicle/Vehicle.d.ts +300 -0
  38. package/dist/types/gl/vehicle/Wheel.d.ts +147 -0
  39. package/dist/types/inject.d.ts +33 -21
  40. package/dist/types/runtime/files.d.ts +24 -1
  41. package/dist/types/runtime/input.d.ts +11 -0
  42. package/dist/types/scene/defineScene.d.ts +10 -3
  43. package/dist/types/ui/UIImage.d.ts +15 -5
  44. package/dist/types.json +1 -1
  45. package/package.json +1 -1
  46. package/prompts/README.md +142 -142
  47. package/prompts/dist/2d-game.md +408 -197
  48. package/prompts/dist/3d-app.md +491 -166
  49. package/prompts/dist/ar-app.md +373 -163
  50. package/prompts/dist/design.md +83 -87
  51. package/prompts/dist/ui-app.md +325 -136
  52. package/src/audio/Bus.ts +102 -0
  53. package/src/audio/Sound.ts +96 -0
  54. package/src/audio/Voice.ts +102 -0
  55. package/src/audio/audio.ts +161 -0
  56. package/src/audio/support.ts +6 -0
  57. package/src/bridges.d.ts +279 -32
  58. package/src/canvas/Canvas.ts +21 -0
  59. package/src/compile/__tests__/compile.test.ts +11 -0
  60. package/src/compile/compileProject.ts +30 -15
  61. package/src/compile/header.ts +6 -3
  62. package/src/compile/index.ts +4 -0
  63. package/src/compile/sceneEditor.ts +42 -1
  64. package/src/core/Aspect.ts +33 -8
  65. package/src/g2/Scene2D.ts +7 -0
  66. package/src/gl/CameraPlace.ts +52 -52
  67. package/src/gl/DecalSet.ts +360 -0
  68. package/src/gl/Geometry.ts +348 -279
  69. package/src/gl/Light.ts +16 -0
  70. package/src/gl/Lightmap.ts +35 -7
  71. package/src/gl/Material.ts +173 -4
  72. package/src/gl/Mesh.ts +120 -83
  73. package/src/gl/Model.ts +33 -1
  74. package/src/gl/Node.ts +1 -1
  75. package/src/gl/Particles.ts +21 -3
  76. package/src/gl/Scene.ts +41 -7
  77. package/src/gl/Texture.ts +43 -3
  78. package/src/gl/animation/AnimationClip.ts +43 -20
  79. package/src/gl/animation/Animator.ts +4 -3
  80. package/src/gl/animation/DynamicBone.ts +459 -0
  81. package/src/gl/{IK.ts → animation/IK.ts} +4 -4
  82. package/src/gl/{Locomotion.ts → animation/Locomotion.ts} +7 -7
  83. package/src/gl/animation/core.ts +20 -15
  84. package/src/gl/audio/AudioSource.ts +113 -0
  85. package/src/gl/audio/AudioZone.ts +75 -0
  86. package/src/gl/audio/SceneAudio.ts +26 -0
  87. package/src/gl/{NavAgent.ts → nav/NavAgent.ts} +5 -5
  88. package/src/gl/{NavMesh.ts → nav/NavMesh.ts} +8 -8
  89. package/src/gl/{CharacterController.ts → physics/CharacterController.ts} +5 -5
  90. package/src/gl/{Physics.ts → physics/Physics.ts} +12 -5
  91. package/src/gl/{Ragdoll.ts → physics/Ragdoll.ts} +272 -270
  92. package/src/gl/{Shape.ts → physics/Shape.ts} +3 -3
  93. package/src/gl/{Trigger.ts → physics/Trigger.ts} +2 -2
  94. package/src/gl/{physicsEvents.ts → physics/physicsEvents.ts} +1 -1
  95. package/src/gl/scenarios.ts +291 -291
  96. package/src/gl/state.ts +1 -1
  97. package/src/gl/{Terrain.ts → terrain/Terrain.ts} +10 -10
  98. package/src/gl/{terrainMesh.ts → terrain/terrainMesh.ts} +1 -1
  99. package/src/gl/vehicle/Vehicle.ts +666 -0
  100. package/src/gl/vehicle/Wheel.ts +290 -0
  101. package/src/inject.ts +226 -212
  102. package/src/runtime/files.ts +32 -2
  103. package/src/runtime/input.ts +6 -1
  104. package/src/scene/defineScene.ts +26 -10
  105. package/src/scene/gizmos.ts +148 -148
  106. package/src/scene/level.ts +2 -2
  107. package/src/ui/UIImage.ts +21 -7
  108. package/dist/types/gl/Gearbox.d.ts +0 -86
  109. package/dist/types/gl/Vehicle.d.ts +0 -191
  110. package/dist/types/gl/Wheel.d.ts +0 -95
  111. package/src/gl/Gearbox.ts +0 -212
  112. package/src/gl/Vehicle.ts +0 -473
  113. package/src/gl/Wheel.ts +0 -240
  114. /package/dist/types/gl/{physicsEvents.d.ts → physics/physicsEvents.d.ts} +0 -0
@@ -1,6 +1,34 @@
1
1
  import { type FetchResponse } from "../runtime/fetch";
2
2
  import { Node } from "./Node";
3
3
  import { Animator, type LodMode } from "./animation/Animator";
4
+ /** One polygon under a screen point — what `Model.pickTriangle` returns. `bones` are the vertex's raw
5
+ * JOINTS_0 / WEIGHTS_0 pairs (weight > 0; empty = unweighted), `bind` its position in mesh space,
6
+ * `world` its skinned position this frame. */
7
+ export interface TrianglePick {
8
+ /** the Model's root entity, and the mesh node's own entity (0 when the host has no entity for it) */
9
+ entity: number;
10
+ node: number;
11
+ nodeName: string;
12
+ mesh: string;
13
+ primitive: number;
14
+ material: string;
15
+ /** triangle index within the primitive (index-buffer order), and whether the ray came from behind */
16
+ triangle: number;
17
+ backface: boolean;
18
+ distance: number;
19
+ point: [number, number, number];
20
+ bary: [number, number, number];
21
+ skin: string;
22
+ vertices: {
23
+ index: number;
24
+ bind: [number, number, number];
25
+ world: [number, number, number];
26
+ bones: {
27
+ name: string;
28
+ weight: number;
29
+ }[];
30
+ }[];
31
+ }
4
32
  export declare class Model extends Node {
5
33
  /** The model's Animator — always present, its clip table = the GLB's embedded clips. Configure
6
34
  * more (external clips, blend spaces, layers) with `model.aspect(Animator, {...})`. */
@@ -31,6 +59,12 @@ export declare class Model extends Node {
31
59
  get lod(): LodMode;
32
60
  set lod(v: LodMode);
33
61
  private static _cullDefault;
62
+ /** DEBUG: the closest polygon of this model under a screen point (logical px — `Input.mouse.position`,
63
+ * a touch event's clientX/Y), tested against the CPU-skinned CURRENT pose, both faces. Names the
64
+ * triangle, its three vertices (bind + skinned positions) and their raw bone weights, so a stretched
65
+ * or misbound polygon can be traced to its binding. One full CPU skin of the model per call: click-rate
66
+ * only. null = miss, or a host without the pick (desktop today). */
67
+ pickTriangle(screenX: number, screenY: number): TrianglePick | null;
34
68
  /** Duplicate this model — a deep copy of the GLB (meshes, skeleton, animation clips), attached to
35
69
  * the same parent and scene and sharing this model's current transform. The clone has its own
36
70
  * independent animation state (reach it via clone.anim). Mirrors this model's culling flag. */
@@ -116,6 +116,12 @@ type Noise = {
116
116
  strength?: number;
117
117
  frequency?: number;
118
118
  speed?: number;
119
+ /** How much of the displacement a particle earns with AGE (default 0 = all of it from birth).
120
+ * The field is sampled by position, so at 0 every particle born at the same spot is pushed the
121
+ * same way and a plume's root wanders off its emitter by up to `strength` metres; at 1 a particle
122
+ * is born exactly where the emitter put it and drifts into the field over its life — what you
123
+ * want whenever the source is visible (an exhaust pipe, a contact patch, a muzzle). */
124
+ ramp?: number;
119
125
  };
120
126
  /** Ground plane for bouncing particles (all render modes). `height` is in the simulation space:
121
127
  * world y with `space: 'world'`, emitter-local y otherwise. */
@@ -169,6 +175,11 @@ export type ParticlesOptions = ParticlesMaterialOptions & {
169
175
  * without this the engine picks who covers whom per frame (smoke popping over a fireball).
170
176
  * Default 0; a fireball wants 2, its smoke 1, a smoke trail -1. */
171
177
  order?: number;
178
+ /** Coarse draw order among ALL blended draws, 0 (first) … 7 (last) — see `Mesh.renderPriority`.
179
+ * Default 5: meshes sit at 4 and decals at 3, so smoke covers a car's glass and its skid marks
180
+ * whatever the camera does (the engine's depth sort compares object centres, and an emitter's
181
+ * centre says nothing about where its cloud is). `order` breaks ties inside one priority. */
182
+ renderPriority?: number;
172
183
  shape?: Shape;
173
184
  /** Extra velocity over life (radial burst that decays, axis drift) — see `VelocityOverLife`. */
174
185
  velocityOverLife?: VelocityOverLife;
@@ -212,6 +223,8 @@ export declare class Particles extends Node {
212
223
  set inheritVelocity(val: number);
213
224
  set rateOverDistance(val: number);
214
225
  set order(val: number);
226
+ /** Coarse draw order, 0 … 7 — see `Mesh.renderPriority`. Write-only. */
227
+ set renderPriority(v: number);
215
228
  set velocityOverLife(v: VelocityOverLife);
216
229
  set shape(shape: Shape);
217
230
  /** (mesh mode) Replace the particle mesh; live particles keep flying as the new shape. */
@@ -4,6 +4,7 @@ import { Presentable, type PresentOptions } from "../ui/presentable";
4
4
  import type { ClickEvent, TouchStartEvent } from "../runtime/touch";
5
5
  import type { FetchResponse } from "../runtime/fetch";
6
6
  import { Camera } from "./Camera";
7
+ import { SceneAudio } from "./audio/SceneAudio";
7
8
  import { type ControlsHandle, type ControlsOptions } from "./controls";
8
9
  import { Material } from "./Material";
9
10
  import { Node } from "./Node";
@@ -95,6 +96,9 @@ export type SceneOptions = {
95
96
  * takes over). Unset keeps the host default (desktop 4×, mobile/web off). The biggest single
96
97
  * fill-rate cost after resolution — turn it down on big screens before anything else. */
97
98
  antialias?: boolean | 2 | 4;
99
+ /** Keep a stencil buffer for this scene (off by default: it costs memory and a clear per frame).
100
+ * Needed before any material's `stencil` test or write does anything. */
101
+ stencil?: boolean;
98
102
  /** Render the 3D at this fraction of the viewport (0.25–1) and upscale; the UI stays at native
99
103
  * resolution. A fixed, predictable cut of per-pixel GPU work — `0.75` is ~45 % cheaper and
100
104
  * barely visible in motion, `0.5` quarters it. Headless renders ignore it. */
@@ -118,6 +122,10 @@ export type SceneOptions = {
118
122
  * scene file — `env` is applied before the nodes build) and leaves already-loaded ones alone.
119
123
  * On the desktop host `CREATOR_TEXTURE_ANISOTROPY` overrides it, for tuning without a rebuild. */
120
124
  anisotropy?: number;
125
+ /** Engine-wide cap on texture size, 0 / unset = none (see `Texture.maxSize`): a KTX2 above it
126
+ * loses its top mip levels on load, a glTF image is downsampled. Applied before this scene's
127
+ * assets load; like `anisotropy` it does not touch textures already loaded. */
128
+ maxTextureSize?: number;
121
129
  /** The look on an HDR display (a screen with headroom above SDR white — Apple XDR panels, the
122
130
  * macOS host today); ignored on SDR. `strength` 0..1 is how much of the picture reaches for the
123
131
  * display's headroom (0 only what SDR clipped, 1 nearly everything; default 0.35). `paperWhite`
@@ -132,6 +140,8 @@ export type SceneOptions = {
132
140
  };
133
141
  export declare class Scene implements Presentable {
134
142
  readonly camera: Camera;
143
+ /** The listener + global 3D audio knobs (docs/audio-plan.md). */
144
+ readonly audio: SceneAudio;
135
145
  readonly _touchStartListeners: Array<(ev: TouchStartEvent<Node | null>) => void>;
136
146
  private _material?;
137
147
  private static _active;
@@ -153,6 +163,19 @@ export declare class Scene implements Presentable {
153
163
  setFog(options: FogOptions | false): void;
154
164
  setMaterialGlobalParameter(i: number, x: number, y: number, z: number, w: number): void;
155
165
  setAntialias(enabled: boolean, scale?: number): void;
166
+ /** Depth-reading effects on / off (a graphics-settings menu): soft particles and projected decals
167
+ * read the scene depth, which costs a depth pre-pass of every opaque draw (~12 % of a fill-bound
168
+ * frame). Off = hard-edged particles, no decals, no pre-pass. Engine-wide, live. */
169
+ setDepthEffects(enabled: boolean): void;
170
+ /** LOD distance (a graphics-settings menu): the engine's LOD thresholds × `bias`. 2 = every level
171
+ * switches at half the distance (a model must look twice as big on screen to keep its detail),
172
+ * 0.5 = full detail twice as far, 1 = the defaults. Engine-wide, live. Only GLBs that carry
173
+ * `_LOD<n>` meshes (`lecodes assets doctor --lod`) have levels to switch. */
174
+ setLodBias(bias: number): void;
175
+ /** Runtime form of `bloom` / `bloomIntensity` (a graphics-settings menu). */
176
+ setBloom(enabled: boolean, intensity?: number): void;
177
+ /** The scene's stencil buffer on / off (see `SceneOptions.stencil`). */
178
+ setStencil(enabled: boolean): void;
156
179
  add(...nodes: Node[]): this;
157
180
  remove(...nodes: Node[]): this;
158
181
  /** Attach (and configure) a system, or reconfigure it if already present. Returns the scene typed
@@ -1,5 +1,28 @@
1
1
  import { type FetchResponse, type File } from "../runtime/fetch";
2
+ /** `Texture.load` options. */
3
+ export type TextureLoadOptions = {
4
+ /** `true` (default): colour, stored sRGB. `false`: data (normal map, mask, heightmap) — kept linear. */
5
+ srgb?: boolean;
6
+ /** Ignore `Texture.maxSize` for this texture (a lightmap page, a lookup table). */
7
+ fullSize?: boolean;
8
+ };
2
9
  export declare class Texture {
10
+ static _maxSize: number;
11
+ /** Engine-wide cap on texture size (a "texture quality" setting), 0 = none. A KTX2 wider or
12
+ * taller than this loses its top mip levels on load (nothing resampled, less memory and
13
+ * bandwidth), a glTF PNG/JPEG is downsampled. Reaches textures loaded AFTER it is set — a loaded
14
+ * level keeps its textures — so set it up front (`SceneOptions.maxTextureSize`, or before the
15
+ * level loads) and apply a menu change on the next level load. Lightmap pages are exempt
16
+ * (`TextureLoadOptions.fullSize`). A host may pin it (desktop `CREATOR_TEXTURE_MAX_SIZE`). */
17
+ static _anisotropy: number;
18
+ /** Engine-wide anisotropic filtering, 1 (off) … 16 (default 2; `SceneOptions.anisotropy` sets it up
19
+ * front). A sampler is baked when its texture is bound, so like `maxSize` this reaches textures
20
+ * loaded AFTER it — set it before the level loads. Measured on a lightmapped interior at 720p:
21
+ * 4× costs ~20 % of the frame over 1× on an integrated GPU. A host may pin it. */
22
+ static get anisotropy(): number;
23
+ static set anisotropy(level: number);
24
+ static get maxSize(): number;
25
+ static set maxSize(size: number);
3
26
  readonly width: number;
4
27
  readonly height: number;
5
28
  /** Horizontal wrap mode (U). */
@@ -23,5 +46,10 @@ export declare class Texture {
23
46
  }): Texture;
24
47
  /** Re-upload a rectangle of a `fromPixels` texture (same channel count). */
25
48
  update(x: number, y: number, width: number, height: number, data: Uint8Array): void;
26
- static load(source: string | FetchResponse | File): Promise<Texture>;
49
+ /** Decode an image (PNG / JPG, or a KTX2 the core transcodes) into a texture. `srgb` (default
50
+ * true) says the bytes are COLOUR, stored sRGB so the GPU linearises them on sample; pass
51
+ * `false` for DATA — a normal map, a mask, a heightmap — which must come back as stored (a
52
+ * flat normal read through sRGB bends by ~35°). A KTX2 decides by its own header. Image
53
+ * textures get a mip chain (trilinear) on hosts that build one. */
54
+ static load(source: string | FetchResponse | File, options?: TextureLoadOptions): Promise<Texture>;
27
55
  }
@@ -22,6 +22,19 @@ export type ClipInfo = {
22
22
  duration: number;
23
23
  trackCount: number;
24
24
  };
25
+ /** What `AnimationClip.from(clip, …)` derives from a clip. */
26
+ export type DeriveOptions = {
27
+ /** The clip MIRRORED — left ↔ right, the motion on the other side of the body (a stop that brakes on the
28
+ * left foot brakes on the right). Derived on the rig of the clip's own file (joints pair by name, the
29
+ * sagittal plane comes from the rest pose), so the result is the same on every model: its contacts,
30
+ * phase, root motion and heading are the mirrored ones, its name is `<name>_M`. Only clips from a GLB
31
+ * carry a rig; a curve-built clip cannot be mirrored. */
32
+ mirror?: boolean;
33
+ /** Start of the window, seconds of the source (default 0). */
34
+ from?: number;
35
+ /** End of the window, seconds of the source (default the clip's end). */
36
+ to?: number;
37
+ };
25
38
  export declare class AnimationClip {
26
39
  /** Source name (the clip's name inside its file; `"clip"` for procedural clips). Informational —
27
40
  * an Animator addresses clips by the key YOU give it. */
@@ -33,8 +46,8 @@ export declare class AnimationClip {
33
46
  private constructor();
34
47
  /** Mark a moment of the clip (SECONDS from its start) with an event name: `kick.addEvent(0.4, 'hit')`
35
48
  * → `anim.on('hit', (clip, layer) => …)` fires when the playhead crosses it, loops included.
36
- * Events are part of the clip: every model playing it gets them; `slice()` keeps the ones inside
37
- * the range, re-timed. Chainable. */
49
+ * Events are part of the clip: every model playing it gets them; `AnimationClip.from(clip, { from, to })`
50
+ * keeps the ones inside the window, re-timed. Chainable. */
38
51
  addEvent(time: number, name: string): this;
39
52
  private _pushEvents;
40
53
  /** Load ONE clip from a GLB: the file's only/first clip, or the one named / at the given index. */
@@ -43,18 +56,18 @@ export declare class AnimationClip {
43
56
  static loadAll(source: string | FetchResponse): Promise<Record<string, AnimationClip>>;
44
57
  /** Build a clip from curves in code — no DCC needed. Keys are `[time, value]`; a track binds to the
45
58
  * node of that name when the clip is used by an Animator (bones, or any child node). */
46
- static from(def: ClipDef): AnimationClip;
47
- /** A sub-range of this clip as a NEW clip — `[start, end]` seconds of the source, re-timed to 0
48
- * (`end` defaults to the clip's end; array-slice semantics). Cut a too-long take down to the
49
- * action, or carve several sub-clips out of one packed timeline:
59
+ static fromCurves(def: ClipDef): AnimationClip;
60
+ /** A NEW clip derived from `clip`: its mirror (`{ mirror: true }` — the other side of the body), a window
61
+ * of it (`{ from, to }` seconds of the source, re-timed to 0; array-slice semantics), or both. Cut a
62
+ * too-long take down to the action, carve several sub-clips out of one packed timeline, get the
63
+ * left-footed stop from the right-footed one:
50
64
  *
51
- * clips: { Kick: kick.slice(0.2, 1.1) }
65
+ * clips: { Kick: AnimationClip.from(kick, { from: 0.2, to: 1.1 }), StopM: AnimationClip.from(stop, { mirror: true }) }
52
66
  *
53
- * Keys outside the range are dropped and exact boundary values are interpolated in, so the
54
- * sliced clip starts and ends precisely on the source's pose at the cut points. Everything
55
- * downstream (blend spaces, events' 0–1 times, root motion, phase sync) sees a normal clip of
56
- * the new duration. */
57
- slice(start: number, end?: number): AnimationClip;
67
+ * A window drops the keys outside it and interpolates exact boundary values in, so the clip starts and
68
+ * ends precisely on the source's pose at the cut points. Everything downstream (blend spaces, events'
69
+ * times, root motion, foot contacts, phase sync) sees a normal clip. */
70
+ static from(clip: AnimationClip, options: DeriveOptions): AnimationClip;
58
71
  private static _label;
59
72
  private static _loadSet;
60
73
  }
@@ -0,0 +1,173 @@
1
+ import { Aspect } from "../../core/Aspect";
2
+ import type { FieldMeta } from "../../core/fields";
3
+ import { Vec3, type Vec3Like } from "../../math/vec";
4
+ import { Node } from "../Node";
5
+ /** One value for the chain, or `[root, tip]` interpolated down it by depth. */
6
+ export type DynamicBoneCurve = number | readonly [root: number, tip: number];
7
+ /** `'none'`, `'probe'` (a ray from the chain root down against the physics world, every frame), a
8
+ * height (a horizontal plane at that world y) or a world point (a plane through it, `floorNormal` up). */
9
+ export type DynamicBoneFloor = "none" | "probe" | number | Vec3Like;
10
+ /** `'auto'` = every DynamicBoneCollider under the model; `'humanoid'` = those plus capsules generated
11
+ * on the standard humanoid bones (hips, spine, head, limbs — the Ragdoll's layout); `'none'`; or the
12
+ * nodes carrying the colliders to use. */
13
+ export type DynamicBoneColliders = "auto" | "humanoid" | "none" | Node[];
14
+ /** The cloth's OUTSIDE, which makes every collider one-sided: `'none'` = a collider pushes a bone to its
15
+ * nearest surface (tails, ropes); `'auto'` = away from the root bone's own axis (a cloak's strips round a
16
+ * spine face out from it); a vector = a fixed direction in the root bone's frame (a cape on the back:
17
+ * `[0, 0, -1]`). With a side the body is under the cloth by definition: an arm that swung through the
18
+ * cape pushes it away, and a bone that ended up inside a capsule leaves it on the outside — never
19
+ * through it to be held against the chest from the wrong side. */
20
+ export type DynamicBoneSide = "none" | "auto" | Vec3Like;
21
+ /** A capsule (or a sphere) riding a bone — what dynamic bones stay out of. Attach to a body bone:
22
+ * `hips.aspect(DynamicBoneCollider, { radius: 0.12, to: 'Spine' })`. The capsule runs from
23
+ * `offset` (the bone's origin by default) to the `to` bone's origin, or to `end` (both in the bone's
24
+ * local space); neither = a sphere at `offset`. Picked up by every DynamicBone on the same model
25
+ * with `colliders: 'auto'` / `'humanoid'`, or listed explicitly. */
26
+ export declare class DynamicBoneCollider extends Aspect<"dynamicBoneCollider", Node> {
27
+ static readonly aspect = "dynamicBoneCollider";
28
+ /** Radius in metres, in the world: the bone's scale does not touch it (`offset` / `end` are in the bone's own units). */
29
+ radius: number;
30
+ /** Start of the capsule in the bone's local space (default: the bone's origin). */
31
+ offset: Vec3Like;
32
+ /** End of the capsule in the bone's local space (a sphere when neither `end` nor `to` is set). */
33
+ end?: Vec3Like;
34
+ /** The bone the capsule reaches (its origin), instead of `end`. */
35
+ to?: string;
36
+ static fields: FieldMeta<DynamicBoneCollider>;
37
+ private _id;
38
+ private _segment;
39
+ onAttach(): void;
40
+ onReconfigure(): void;
41
+ onDetach(): void;
42
+ }
43
+ export declare class DynamicBone extends Aspect<"dynamicBone", Node> {
44
+ static readonly aspect = "dynamicBone";
45
+ /** Only these bones (by name) join the chain — the root always does, a bone left out takes its
46
+ * subtree with it. Default: the root's whole subtree. */
47
+ bones?: string[];
48
+ /** Depth limit under the root (0 = no limit). */
49
+ depth: number;
50
+ /** Particle radius for the collisions, metres. */
51
+ radius: DynamicBoneCurve;
52
+ /** 0..1 — how fast a bone returns to the animated shape (per 1/60 s). 0 = a rope, 1 = rigid. */
53
+ stiffness: DynamicBoneCurve;
54
+ /** 0..1 — velocity lost per 1/60 s. 0.02 swings like a rope, 0.1 settles like a tail, 0.3 is honey. */
55
+ damping: DynamicBoneCurve;
56
+ /** m/s² downward. A hanging rest pose feels none of it (the bone length cancels it); a pose that
57
+ * sticks out droops by as much as `stiffness` lets it. */
58
+ gravity: DynamicBoneCurve;
59
+ /** Max deviation from the animated direction, degrees per bone (0 = free). */
60
+ angleLimit: DynamicBoneCurve;
61
+ /** Relative mass per bone. A bone-length constraint moves its two ends in inverse proportion to
62
+ * their masses (the root is kinematic): `[3, 1]` makes a tail's base carry its tip, `1` shares
63
+ * evenly like a rope. */
64
+ mass: DynamicBoneCurve;
65
+ /** Bones (by name) that ride the animation exactly while their subtrees still simulate: a sheet's
66
+ * several roots under one anchor (a cloak's seven strips under the spine), so ONE chain owns them all
67
+ * and `link` ties neighbouring strips. */
68
+ pinned?: string[];
69
+ /** Metres a PINNED bone may be pushed off its animated place by a collider — a soft pin. Its particle
70
+ * collides and takes the edge pushes like a free bone (the strip below hangs from where it IS) and is
71
+ * drawn back to the animation when the collider leaves; the bone's local translation follows. A hard
72
+ * pin (0) at a capsule's edge made the first free bone below it jitter: the edge push on a segment
73
+ * with an immovable end is a lever, and a touch near the root threw the child out. 2–3 cm for a
74
+ * cloak's roots under a shoulder capsule. */
75
+ pinGive: number;
76
+ /** 0..1 of the character's travel the chain takes rigidly (0 = the full whip on a dash, 1 = it
77
+ * moves with the body and only the pose's own motion swings it). */
78
+ follow: number;
79
+ /** World wind, m/s². */
80
+ wind: Vec3Like;
81
+ /** The BODY'S TURN, ≥ 0. Above 0 the chain lives in the parent bone's ROTATING frame: `damping` acts
82
+ * against the body's rigid motion (its travel plus its spin), so a heavily damped cloak RIDES a turn —
83
+ * sweeps round with the back — instead of standing in the world while the character spins under it and
84
+ * being dragged round after. The turn's inertia comes back as the frame's forces, scaled by this value:
85
+ * a centrifugal push `ω² · r` along each bone's animated direction from the axis (the hem, farthest
86
+ * from the spine, flies out and, on the chain's length, up). The frame's own physics is built in: the
87
+ * whip back when the turn starts and forward when it stops, Coriolis. 1 = the physical push; more = a
88
+ * bigger fling. With low damping the frame reproduces a free particle (nothing is counted twice);
89
+ * an animation's spine sway is ~1 % of a 900°/s turn. A rotation over 90° in one frame (a respawn
90
+ * facing) counts as a snap, not a spin. 0 = the plain translational frame, no turn forces. */
91
+ spin: number;
92
+ /** With `spin`: the cloth's INERTIA against the body's turn, seconds — the cloth's own rotation follows the
93
+ * body's with this time constant. It falls behind when a turn starts (by about ω·τ: 45° at 900°/s and 0.05),
94
+ * rides once caught up, and keeps turning past the back when the body stops (the same angle, eased out over
95
+ * τ). Smooth by construction. 0 = glued to the turn; large = the cloth stays in the world while the body spins. */
96
+ spinInertia: number;
97
+ /** 0..1 strength of the ties between neighbouring columns at the same depth (a cloak). */
98
+ link: number;
99
+ /** 0..1 blend from the animation (0) to the simulation (1). At 0 nothing is simulated and the
100
+ * chain re-arms on the animated pose when it comes back. */
101
+ weight: number;
102
+ /** Substep rate, Hz (≤ 4 substeps per frame). */
103
+ rate: number;
104
+ /** A root jump longer than this in one frame (metres) resets the chain instead of whipping it. */
105
+ teleport: number;
106
+ /** What the particles stay above: `'none'`, `'probe'`, a height, or a world point. */
107
+ floor: DynamicBoneFloor;
108
+ /** The floor plane's normal when `floor` is a point. */
109
+ floorNormal: Vec3Like;
110
+ /** Friction against the floor, a Coulomb coefficient: a resting bone's slide loses up to
111
+ * `floorFriction · gravity · dt` of speed per substep (0 = ice, 1 = a rubber sole). While a bone
112
+ * rests on the floor or a collider, `stiffness` and `angleLimit` let go of it: the surface's
113
+ * reaction outranks the spring, or a hem folded on the floor would run away along it. */
114
+ floorFriction: number;
115
+ /** Constraint passes per substep (1..8, default 4). Each pass shares every bone length between its
116
+ * ends, so a pull needs passes to travel down a long chain: a long rope may want 8, a short tail is
117
+ * fine with 2. */
118
+ iterations: number;
119
+ /** What the chain collides with (see DynamicBoneColliders). */
120
+ colliders: DynamicBoneColliders;
121
+ /** The cloth's outside — makes the colliders one-sided (see DynamicBoneSide). */
122
+ side: DynamicBoneSide;
123
+ /** How firmly `guide()` targets are HELD through the constraint passes, 0..1. At 0 a guide is applied once
124
+ * before the passes and the length / link passes may drag the bone back toward its un-guided neighbours
125
+ * within the same frame; at 0.5 it is re-pulled after every pass with half its weight (converges with the
126
+ * passes), at 1 with its full weight (a target the bone length cannot reach then jitters). */
127
+ guideHold: number;
128
+ /** The chain's EDGES collide with the capsules too — every bone segment and every link, not only the bones'
129
+ * particles: a capsule thinner than a bone's length (a forearm) no longer passes through the cloth between
130
+ * two bones. Off = particles only. */
131
+ edges: boolean;
132
+ static fields: FieldMeta<DynamicBone>;
133
+ private _id;
134
+ private _chain;
135
+ private _depths;
136
+ private _blob;
137
+ private _sent;
138
+ private _floorKey;
139
+ private _colliderKey;
140
+ private _collidersDirty;
141
+ private _collidersRef;
142
+ private _own;
143
+ private _colliderSet;
144
+ /** The bones of the chain, the root first. */
145
+ get chain(): readonly Node[];
146
+ onAttach(): void;
147
+ onReconfigure(): void;
148
+ /** Every field is live: what changed since the last frame is pushed here, before the engine's stage. */
149
+ update(): void;
150
+ onDetach(): void;
151
+ /** Snap the chain onto the animated pose next frame (a teleport, a camera cut). */
152
+ reset(): void;
153
+ /** GUIDES: world points the bones' particles are drawn to each substep before the constraints —
154
+ * weight 1 = there before them, the rest of the chain hangs, collides and links as before (a cape's
155
+ * strips riding the arms). Replaces the previous set; an empty list clears. A late-phase script
156
+ * (after the animator, before the engine's chain stage) refreshes it every frame. */
157
+ guide(rows: readonly {
158
+ bone: Node;
159
+ at: Vec3Like;
160
+ weight: number;
161
+ }[]): void;
162
+ /** The particles' world positions (the bones, then the leaves' virtual tips) — for a debug draw. */
163
+ get particles(): Vec3[];
164
+ private _collect;
165
+ private _fill;
166
+ /** Neighbour links: the bones at the same depth, in tree order, tied pairwise. */
167
+ private _links;
168
+ private _sync;
169
+ private _pushFloor;
170
+ private _pushColliders;
171
+ /** Capsules on the standard humanoid bones, sized like the Ragdoll's parts. */
172
+ private _makeHumanoid;
173
+ }
@@ -1,7 +1,7 @@
1
- import { Aspect } from "../core/Aspect";
2
- import { type Vec3Like } from "../math/vec";
3
- import { Quat } from "../math/quat";
4
- import type { Node } from "./Node";
1
+ import { Aspect } from "../../core/Aspect";
2
+ import { type Vec3Like } from "../../math/vec";
3
+ import { Quat } from "../../math/quat";
4
+ import type { Node } from "../Node";
5
5
  type Target = Node | Vec3Like;
6
6
  /** Two-bone analytic IK (limbs). Attach to the END bone: `foot.aspect(IK.TwoBone, { target })`
7
7
  * solves upper (grandparent) + mid (parent) so the end reaches `target`; `pole` steers the bend
@@ -1,9 +1,9 @@
1
- import { Aspect } from "../core/Aspect";
2
- import { Vec3, type Vec3Like } from "../math/vec";
3
- import { Animator } from "./animation/Animator";
4
- import type { FeetOptions } from "./animation/Feet";
5
- import type { WarpOptions } from "./animation/Warp";
6
- import type { Node } from "./Node";
1
+ import { Aspect } from "../../core/Aspect";
2
+ import { Vec3, type Vec3Like } from "../../math/vec";
3
+ import { Animator } from "./Animator";
4
+ import type { FeetOptions } from "./Feet";
5
+ import type { WarpOptions } from "./Warp";
6
+ import type { Node } from "../Node";
7
7
  /** How fast the character wants to go: the gait picks its clips and its speed. */
8
8
  export type Gait = "walk" | "run" | "sprint";
9
9
  /** What the engine shows: the idle, a start, the gait loop, a turn while moving, a stop, or a turn on the spot. */
@@ -6,18 +6,17 @@ import { Vec3 } from "../../math/vec";
6
6
  import type { Node } from "../Node";
7
7
  /** A position on a blend axis (1D) or plane (2D). */
8
8
  export type BlendPosition = number | readonly [number, number];
9
- /** The transition time (seconds) a play / playLoop without `fade` uses. */
10
- export declare const DEFAULT_FADE = 0.2;
11
9
  export type PlayOptions = {
12
- /** Transition seconds — how long the difference to the pose the layer SHOWED takes to decay
13
- * (default 0.2; `0` = cut). Also the transition back at the end unless `fadeOut` overrides it. */
10
+ /** Transition seconds — how long the difference to the pose the layer SHOWED (its loop, a one-shot,
11
+ * or rest) takes to decay. Default `0` = cut: a fade is always asked for, never implied. Also the
12
+ * transition back to the loop at the end unless `fadeOut` overrides it. */
14
13
  fade?: number;
15
14
  /** Transition seconds for the way in only (overrides `fade`). */
16
15
  fadeIn?: number;
17
- /** One-shots: the transition BACK at the end (overrides `fade`). It starts this long before the
18
- * clip ends — while it still plays — so the hand-over lands on the last pose, not after it. On a
19
- * layer with no loop to return to, giving `fadeOut` releases the layer at the end instead of
20
- * holding the last frame. */
16
+ /** One-shots: the transition BACK to the loop at the end (overrides `fade`; `0` = cut at the end).
17
+ * It starts this long before the clip ends — while it still plays — so the hand-over lands on the
18
+ * last pose, not after it. On a layer with no loop the clip HOLDS its last frame (a rock that broke
19
+ * stays broken) — `anim.stop({ fade })` is the way back to rest. */
21
20
  fadeOut?: number;
22
21
  /** Playback rate for this clip (default 1). */
23
22
  speed?: number;
@@ -39,8 +38,8 @@ export type StopOptions = {
39
38
  fade?: number;
40
39
  };
41
40
  export type LoopOptions = {
42
- /** Transition seconds from whatever the layer shows — its previous loop, or a one-shot still
43
- * playing (default 0.2; `0` = cut). */
41
+ /** Transition seconds from whatever the layer shows — its previous loop, a one-shot still playing,
42
+ * or rest. Default `0` = cut: a loop wanting a transition asks for one (`{ fade: 0.2 }`). */
44
43
  fade?: number;
45
44
  /** Playback rate of the loop's clips (default 1). */
46
45
  speed?: number;
@@ -165,6 +164,12 @@ export type ClipInfo = {
165
164
  turnAt(time: number): number;
166
165
  /** the root's own speed at `time`, m/s — how fast the clip is moving the body right there */
167
166
  speedAt(time: number): number;
167
+ /** the PELVIS' yaw relative to the clip's heading at `time` (rad, + = left, wrapped to ±π): the stance twist
168
+ * the heading leaves out — an idle stands ~43° off the course it starts along, a stop's tail turns into that
169
+ * stance while the heading holds. A controller that switches clips mid-pose can keep the PELVIS continuous
170
+ * (turn the node by the difference between the two clips' values) where keeping the course would swing
171
+ * the body. Without a baked heading: the pelvis' yaw at frame 0 throughout. */
172
+ pelvisYawAt(time: number): number;
168
173
  /** the unit travel direction at `time` ([x, z], model space, held through stills) — integrate
169
174
  * direction × d(travel) to reconstruct the root's 2D path (a treadmill display, a turn's arc) */
170
175
  directionAt(time: number): [number, number];
@@ -0,0 +1,60 @@
1
+ import { Aspect } from "../../core/Aspect";
2
+ import type { FieldMeta } from "../../core/fields";
3
+ import { type PlaySoundOptions, type Rolloff } from "../../audio/audio";
4
+ import type { Sound } from "../../audio/Sound";
5
+ import type { Voice } from "../../audio/Voice";
6
+ import type { Node } from "../Node";
7
+ export type AudioCone = {
8
+ /** Degrees of full volume around the node's −Z. */
9
+ inner: number;
10
+ /** Degrees where the volume has fallen to `outerGain`. */
11
+ outer: number;
12
+ /** Volume behind the source, 0 … 1. */
13
+ outerGain?: number;
14
+ };
15
+ export declare class AudioSource extends Aspect<"audio", Node> {
16
+ static readonly aspect = "audio";
17
+ static fields: FieldMeta<AudioSource>;
18
+ private _minDistance;
19
+ private _maxDistance;
20
+ private _rolloff;
21
+ private _cone;
22
+ private _doppler;
23
+ private _spread;
24
+ private _occlusion;
25
+ private _bus;
26
+ /** Metres of full volume around the node. Default 1. */
27
+ get minDistance(): number;
28
+ set minDistance(v: number);
29
+ /** Metres beyond which the source no longer gets quieter. Default 50. */
30
+ get maxDistance(): number;
31
+ set maxDistance(v: number);
32
+ /** `inverse` (default), `linear`, `exponential`, `none`. */
33
+ get rolloff(): Rolloff;
34
+ set rolloff(v: Rolloff);
35
+ /** Directional source along the node's −Z; null = omnidirectional (default). */
36
+ get cone(): AudioCone | null;
37
+ set cone(v: AudioCone | null);
38
+ /** Doppler amount 0 … 1 (0 = off, the default) — needs the node to actually move. */
39
+ get doppler(): number;
40
+ set doppler(v: number);
41
+ /** 0 = pin-point panning (default) … 1 = the same on every speaker (a big, close source). */
42
+ get spread(): number;
43
+ set spread(v: number);
44
+ /** The engine raycasts listener → source (against solid bodies) and muffles the voices when
45
+ * something is in the way. Default false. */
46
+ get occlusion(): boolean;
47
+ set occlusion(v: boolean);
48
+ /** Default bus for voices on this source. Default `sfx`. */
49
+ get bus(): string;
50
+ set bus(v: string);
51
+ /** Live voices on this source. */
52
+ get voices(): number;
53
+ onAttach(): void;
54
+ onDetach(): void;
55
+ /** Play a clip from this node. */
56
+ play(sound: Sound, options?: PlaySoundOptions): Voice;
57
+ /** Stop every voice on this source (fade in seconds). */
58
+ stopAll(fade?: number): void;
59
+ private _push;
60
+ }
@@ -0,0 +1,32 @@
1
+ import { Aspect } from "../../core/Aspect";
2
+ import { type ReverbParams, type ReverbPreset } from "../../audio/Bus";
3
+ import { type Vec3Like } from "../../math/vec";
4
+ import type { Node } from "../Node";
5
+ export declare class AudioZone extends Aspect<"audioZone", Node> {
6
+ static readonly aspect = "audioZone";
7
+ private _box;
8
+ private _sphere;
9
+ private _reverb;
10
+ private _blend;
11
+ private _bus;
12
+ /** The engine's zone id — what `audio.stats.listenerZone` reports while the listener is inside. */
13
+ get id(): number;
14
+ /** Half-extents [hx, hy, hz]; falls back to the node's Shape box, then a 1 m cube. */
15
+ get box(): Vec3Like | null;
16
+ set box(v: Vec3Like | null);
17
+ /** Radius; falls back to the node's Shape sphere. */
18
+ get sphere(): number | null;
19
+ set sphere(v: number | null);
20
+ /** A preset name or explicit params (see Bus.reverb). */
21
+ get reverb(): ReverbPreset | ReverbParams;
22
+ set reverb(v: ReverbPreset | ReverbParams);
23
+ /** Crossfade depth in metres inside the border (0 = a hard edge). Default 1. */
24
+ get blend(): number;
25
+ set blend(v: number);
26
+ /** The bus the zone's reverb rides on. Default `sfx`. */
27
+ get bus(): string;
28
+ set bus(v: string);
29
+ onAttach(): void;
30
+ onDetach(): void;
31
+ private _push;
32
+ }
@@ -0,0 +1,11 @@
1
+ import type { Node } from "../Node";
2
+ export declare class SceneAudio {
3
+ private _listener;
4
+ private _doppler;
5
+ /** The node the engine listens from; null (default) = the active camera. */
6
+ get listener(): Node | null;
7
+ set listener(node: Node | null);
8
+ /** Multiplies every source's doppler amount (0 = off everywhere). Default 1. */
9
+ get dopplerFactor(): number;
10
+ set dopplerFactor(v: number);
11
+ }
@@ -1,8 +1,8 @@
1
- import { Aspect } from "../core/Aspect";
2
- import type { FieldMeta } from "../core/fields";
3
- import { Vec3, type Vec3Like } from "../math/vec";
1
+ import { Aspect } from "../../core/Aspect";
2
+ import type { FieldMeta } from "../../core/fields";
3
+ import { Vec3, type Vec3Like } from "../../math/vec";
4
4
  import { NavCrowd } from "./NavMesh";
5
- import type { Node } from "./Node";
5
+ import type { Node } from "../Node";
6
6
  export type NavAgentState = "idle" | "moving" | "arrived" | "blocked" | "offmesh";
7
7
  export type NavAgentAvoidance = "off" | "low" | "medium" | "high";
8
8
  export type NavAgentDrive = "position" | "controller";
@@ -1,7 +1,7 @@
1
- import { System } from "../core/Aspect";
2
- import { Vec3, type Vec3Like } from "../math/vec";
3
- import type { Node } from "./Node";
4
- import type { Scene } from "./Scene";
1
+ import { System } from "../../core/Aspect";
2
+ import { Vec3, type Vec3Like } from "../../math/vec";
3
+ import type { Node } from "../Node";
4
+ import type { Scene } from "../Scene";
5
5
  export declare const NAV_AGENT: {
6
6
  readonly RADIUS: 0;
7
7
  readonly HEIGHT: 1;