lecodes-sdk 0.20.2 → 1.1.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 (129) hide show
  1. package/dist/global.d.ts +35 -9
  2. package/dist/types/animate/tween/Animation.d.ts +69 -0
  3. package/dist/types/animate/tween/Timeline.d.ts +55 -0
  4. package/dist/types/animate/tween/animateValue.d.ts +27 -0
  5. package/dist/types/animate/tween/easing.d.ts +29 -0
  6. package/dist/types/animate/tween/spec.d.ts +178 -0
  7. package/dist/types/canvas/Canvas.d.ts +2 -0
  8. package/dist/types/g2/Node2D.d.ts +16 -0
  9. package/dist/types/g2/Sprite.d.ts +11 -1
  10. package/dist/types/gl/Camera.d.ts +15 -1
  11. package/dist/types/gl/DecalSet.d.ts +48 -3
  12. package/dist/types/gl/Foliage.d.ts +47 -0
  13. package/dist/types/gl/Geometry.d.ts +36 -0
  14. package/dist/types/gl/Light.d.ts +25 -7
  15. package/dist/types/gl/Lightmap.d.ts +90 -51
  16. package/dist/types/gl/Material.d.ts +32 -20
  17. package/dist/types/gl/Mesh.d.ts +7 -1
  18. package/dist/types/gl/Model.d.ts +41 -5
  19. package/dist/types/gl/Node.d.ts +18 -0
  20. package/dist/types/gl/Particles.d.ts +53 -1
  21. package/dist/types/gl/Scene.d.ts +21 -1
  22. package/dist/types/gl/animation/AnimationClip.d.ts +19 -0
  23. package/dist/types/gl/animation/Animator.d.ts +27 -0
  24. package/dist/types/gl/animation/DynamicBone.d.ts +184 -0
  25. package/dist/types/gl/animation/IK.d.ts +109 -0
  26. package/dist/types/gl/{Locomotion.d.ts → animation/Locomotion.d.ts} +6 -6
  27. package/dist/types/gl/animation/Warp.d.ts +2 -1
  28. package/dist/types/gl/animation/core.d.ts +35 -4
  29. package/dist/types/gl/{AudioSource.d.ts → audio/AudioSource.d.ts} +6 -6
  30. package/dist/types/gl/{AudioZone.d.ts → audio/AudioZone.d.ts} +4 -4
  31. package/dist/types/gl/{SceneAudio.d.ts → audio/SceneAudio.d.ts} +1 -1
  32. package/dist/types/gl/{NavAgent.d.ts → nav/NavAgent.d.ts} +4 -4
  33. package/dist/types/gl/{NavMesh.d.ts → nav/NavMesh.d.ts} +4 -4
  34. package/dist/types/gl/{CharacterController.d.ts → physics/CharacterController.d.ts} +5 -5
  35. package/dist/types/gl/{Physics.d.ts → physics/Physics.d.ts} +6 -5
  36. package/dist/types/gl/physics/Ragdoll.d.ts +161 -0
  37. package/dist/types/gl/{Shape.d.ts → physics/Shape.d.ts} +3 -3
  38. package/dist/types/gl/{Trigger.d.ts → physics/Trigger.d.ts} +2 -2
  39. package/dist/types/gl/{Terrain.d.ts → terrain/Terrain.d.ts} +10 -8
  40. package/dist/types/gl/{terrainMesh.d.ts → terrain/terrainMesh.d.ts} +1 -1
  41. package/dist/types/gl/vehicle/Vehicle.d.ts +300 -0
  42. package/dist/types/gl/vehicle/Wheel.d.ts +147 -0
  43. package/dist/types/inject.d.ts +35 -27
  44. package/dist/types/runtime/files.d.ts +24 -1
  45. package/dist/types/scene/defineScene.d.ts +50 -31
  46. package/dist/types/ui/UIButton.d.ts +3 -1
  47. package/dist/types/ui/UIInput.d.ts +5 -1
  48. package/dist/types/ui/UINode.d.ts +24 -24
  49. package/dist/types.json +1 -1
  50. package/package.json +1 -1
  51. package/prompts/README.md +142 -142
  52. package/prompts/core-design.md +27 -4
  53. package/prompts/core.md +35 -6
  54. package/prompts/select.ts +19 -4
  55. package/src/animate/tween/Animation.ts +378 -0
  56. package/src/animate/tween/Timeline.ts +175 -0
  57. package/src/animate/tween/animateValue.ts +100 -0
  58. package/src/animate/tween/easing.ts +172 -0
  59. package/src/animate/tween/spec.ts +479 -0
  60. package/src/bridges.d.ts +1760 -1481
  61. package/src/canvas/Canvas.ts +21 -0
  62. package/src/compile/__tests__/assetMacro.test.ts +26 -0
  63. package/src/compile/__tests__/compile.test.ts +11 -0
  64. package/src/compile/__tests__/detectEntry.test.ts +19 -0
  65. package/src/compile/__tests__/serverSplit.test.ts +27 -0
  66. package/src/compile/bundler.ts +34 -4
  67. package/src/compile/compileProject.ts +31 -1
  68. package/src/compile/detectEntry.ts +8 -3
  69. package/src/compile/header.ts +6 -3
  70. package/src/compile/index.ts +3 -0
  71. package/src/compile/sceneEditor.ts +42 -1
  72. package/src/compile/serverSplit.ts +9 -3
  73. package/src/g2/Node2D.ts +38 -0
  74. package/src/g2/Sprite.ts +20 -1
  75. package/src/gl/Camera.ts +34 -1
  76. package/src/gl/DecalSet.ts +132 -5
  77. package/src/gl/Foliage.ts +102 -0
  78. package/src/gl/Geometry.ts +109 -0
  79. package/src/gl/Light.ts +46 -16
  80. package/src/gl/Lightmap.ts +440 -249
  81. package/src/gl/Material.ts +69 -36
  82. package/src/gl/Mesh.ts +120 -102
  83. package/src/gl/Model.ts +167 -124
  84. package/src/gl/Node.ts +40 -1
  85. package/src/gl/Particles.ts +82 -5
  86. package/src/gl/Scene.ts +35 -2
  87. package/src/gl/animation/AnimationClip.ts +52 -0
  88. package/src/gl/animation/Animator.ts +42 -2
  89. package/src/gl/animation/DynamicBone.ts +482 -0
  90. package/src/gl/animation/IK.ts +214 -0
  91. package/src/gl/{Locomotion.ts → animation/Locomotion.ts} +7 -7
  92. package/src/gl/animation/Playback.ts +5 -4
  93. package/src/gl/animation/Warp.ts +5 -2
  94. package/src/gl/animation/core.ts +65 -4
  95. package/src/gl/{AudioSource.ts → audio/AudioSource.ts} +7 -7
  96. package/src/gl/{AudioZone.ts → audio/AudioZone.ts} +75 -75
  97. package/src/gl/{SceneAudio.ts → audio/SceneAudio.ts} +2 -2
  98. package/src/gl/{NavAgent.ts → nav/NavAgent.ts} +5 -5
  99. package/src/gl/{NavMesh.ts → nav/NavMesh.ts} +8 -8
  100. package/src/gl/{CharacterController.ts → physics/CharacterController.ts} +5 -5
  101. package/src/gl/{Physics.ts → physics/Physics.ts} +12 -5
  102. package/src/gl/physics/Ragdoll.ts +451 -0
  103. package/src/gl/{Shape.ts → physics/Shape.ts} +3 -3
  104. package/src/gl/{Trigger.ts → physics/Trigger.ts} +45 -45
  105. package/src/gl/{physicsEvents.ts → physics/physicsEvents.ts} +1 -1
  106. package/src/gl/{Terrain.ts → terrain/Terrain.ts} +14 -12
  107. package/src/gl/{terrainMesh.ts → terrain/terrainMesh.ts} +1 -1
  108. package/src/gl/vehicle/Vehicle.ts +666 -0
  109. package/src/gl/vehicle/Wheel.ts +290 -0
  110. package/src/inject.ts +236 -224
  111. package/src/runtime/files.ts +32 -2
  112. package/src/scene/defineScene.ts +92 -66
  113. package/src/scene/level.ts +2 -2
  114. package/src/ui/UIButton.ts +2 -2
  115. package/src/ui/UIInput.ts +3 -3
  116. package/src/ui/UINode.ts +61 -36
  117. package/dist/types/animate/animate.d.ts +0 -20
  118. package/dist/types/gl/Gearbox.d.ts +0 -86
  119. package/dist/types/gl/IK.d.ts +0 -53
  120. package/dist/types/gl/Ragdoll.d.ts +0 -86
  121. package/dist/types/gl/Vehicle.d.ts +0 -191
  122. package/dist/types/gl/Wheel.d.ts +0 -95
  123. package/src/animate/animate.ts +0 -238
  124. package/src/gl/Gearbox.ts +0 -212
  125. package/src/gl/IK.ts +0 -193
  126. package/src/gl/Ragdoll.ts +0 -270
  127. package/src/gl/Vehicle.ts +0 -473
  128. package/src/gl/Wheel.ts +0 -240
  129. /package/dist/types/gl/{physicsEvents.d.ts → physics/physicsEvents.d.ts} +0 -0
@@ -24,6 +24,14 @@ export type DecalOptions = {
24
24
  fadeIn?: number;
25
25
  /** Seconds of fade at the end of `life` (ignored with life 0). Default 0. */
26
26
  fadeOut?: number;
27
+ /** Opacity multipliers at the image's BOTTOM and TOP edge (−Y / +Y), interpolated along it — a
28
+ * tire mark that darkens as the slide deepens. Default `[1, 1]`. */
29
+ gradient?: [number, number];
30
+ /** Tilt of the image's bottom and top edge, as a unit-space slope (Δy per Δx across the box):
31
+ * the edge pivots on the corner of the side it keeps and cuts INTO the box on the other, so two
32
+ * boxes can meet on one shared line — what `DecalTrail` uses to mitre its joints. `0` = square.
33
+ * Default `[0, 0]`. */
34
+ caps?: [number, number];
27
35
  };
28
36
  export type DecalSetOptions = DecalOptions & {
29
37
  /** The material — `Material.decal({ map })` by default (`map` below is its shortcut). A custom
@@ -51,8 +59,10 @@ export type DecalSetOptions = DecalOptions & {
51
59
  /** HDR boost of the image (0 = none). Default material only. */
52
60
  emissive?: number;
53
61
  name?: string;
54
- /** Coarse draw order, 0 (first) … 7 (last); default 4 — see `Mesh.renderPriority`. Decals are
55
- * blended and sort with the other blended draws of their priority. */
62
+ /** Coarse draw order, 0 (first) … 7 (last) — see `Mesh.renderPriority`. Default 3: a decal is
63
+ * part of the surface it sits on, so it draws before every other blended thing (meshes 4,
64
+ * particles 5) — the engine's depth sort compares object centres, and a set spread over the
65
+ * level has no useful centre. */
56
66
  renderPriority?: number;
57
67
  };
58
68
  /** `spawn` placement: where the image sits on the surface. */
@@ -99,5 +109,40 @@ export declare class DecalSet extends Node {
99
109
  remove(slot: number): this;
100
110
  clear(): this;
101
111
  private _writePlacement;
102
- private _write;
112
+ }
113
+ /** A `DecalTrail`'s look: the strip's width, and what every segment carries. */
114
+ export type DecalTrailOptions = Omit<DecalOptions, "size" | "caps" | "gradient"> & {
115
+ /** Width of the strip on the surface, world units. */
116
+ width: number;
117
+ };
118
+ /**
119
+ * A continuous strip of decals along a path — a tire's skid mark, a dragged body, a tread track.
120
+ * Feed it points (`add`) as the thing moves; every new point becomes one box-decal SEGMENT from the
121
+ * previous point, its image's up along the travel, and the joints between segments are MITRED: each
122
+ * box is cut along the bisector it shares with its neighbour (`caps`), so the strip has no overlaps
123
+ * darkening the outside of a bend and no wedges of gap on the inside. The opacity given with each
124
+ * point is interpolated along the segment (`gradient`), so a mark can darken as a slide deepens and
125
+ * fade as it ends. Segments are the set's decals — same ring, same `max` budget: a set of 900 holds
126
+ * 900 segments across every trail drawn from it.
127
+ *
128
+ * The previous segment is REWRITTEN when the next point arrives (its far cap becomes the shared
129
+ * mitre), through `DecalSet.update`'s path — a recycled slot is simply left alone by the engine.
130
+ */
131
+ export declare class DecalTrail {
132
+ private _prev;
133
+ private _seg;
134
+ readonly set: DecalSet;
135
+ readonly options: DecalTrailOptions;
136
+ constructor(set: DecalSet, options: DecalTrailOptions);
137
+ /** Extend the strip to `point` on a surface with `normal`, `opacity` (0..1) at that point. The
138
+ * first call after a start / `end()` only anchors the strip. Returns the new segment's slot, or
139
+ * −1 when nothing was drawn (the anchor, a point that did not move, no host support). */
140
+ add(point: Vec3Like, normal: Vec3Like, opacity?: number): number;
141
+ /** Finish the strip. With a `point`, one last segment runs out to it at opacity 0 — a mark that
142
+ * tapers away instead of stopping dead. The next `add` anchors a new strip. */
143
+ end(point?: Vec3Like, normal?: Vec3Like): this;
144
+ private _rewrite;
145
+ /** Write one segment's box: the strip between its joints, extended past each mitred joint by the
146
+ * mitre's reach (w/2 · |slope|) so the tilted cap still passes through the joint's centre. */
147
+ private _box;
103
148
  }
@@ -0,0 +1,47 @@
1
+ import { Model } from "./Model";
2
+ import { Node } from "./Node";
3
+ export type FoliageWind = {
4
+ /** Direction on the ground (x, z); normalised by the engine. Default [1, 0.3]. */
5
+ direction?: [number, number];
6
+ /** Metres of sway at `bendHeight` (taller parts sway more). Default 0.07. 0 = still. */
7
+ strength?: number;
8
+ /** Radians per second of the main sway. Default 1.2. */
9
+ speed?: number;
10
+ /** 0..1: how much the strength breathes over a slow envelope. Default 0.6. */
11
+ gust?: number;
12
+ };
13
+ export type FoliageOptions = {
14
+ /** The wind, or `false` for none. */
15
+ wind?: FoliageWind | false;
16
+ /** Height above the instance origin (m) where the full sway / touch applies; the base never moves.
17
+ * Default 1.5. */
18
+ bendHeight?: number;
19
+ /** Metres the TOP of a small plant leans away from a bender standing at its centre; the whole plant leans
20
+ * as one, rooted at its base. Plants over ~2 m react less, trees over ~5 m not at all (the engine
21
+ * measures each asset). Default 0.6. 0 = no touch reaction. */
22
+ touch?: number;
23
+ /** 0..1: how far each copy's tint drifts from the texture (warmer/darker to cooler/lighter, by a hash
24
+ * of its position). Default 0.35. */
25
+ variation?: number;
26
+ /** 0..1: wrapped sun lighting — thin leaves let light through. Default 0.5. */
27
+ sunWrap?: number;
28
+ };
29
+ export declare const Foliage: {
30
+ /** Level-wide options; each field given replaces the current value (a scene file's `env.foliage`
31
+ * goes through here). */
32
+ configure(options: FoliageOptions): void;
33
+ /** The wind: fields given replace the current ones; `false` = calm. Live. */
34
+ wind(wind: FoliageWind | false): void;
35
+ /** A node whose live position pushes the vegetation within `radius` metres (the hero: ~0.7; a
36
+ * boulder: its size). Any number may be registered; the 8 nearest the camera act each frame. The
37
+ * engine also tracks the node's velocity (smoothed over ~0.2 s): a plant you run through is brushed
38
+ * forward and settles behind you instead of flipping as you cross its centre.
39
+ * Returns the unregister function; a destroyed node drops out on its own. */
40
+ bend(node: Node, radius?: number): () => void;
41
+ /** Stop a node from bending the vegetation. */
42
+ unbend(node: Node): void;
43
+ /** Distance fade for EVERY copy of this model's asset (present and future): the cards thin out
44
+ * from `start` to `end` metres from the camera and past `end` the copy leaves the frame entirely.
45
+ * Small ground cover (grass, clover, flowers) is the target; `end` 0 = no fade. */
46
+ fade(model: Model, start: number, end: number): void;
47
+ };
@@ -1,5 +1,19 @@
1
1
  import { type Vec3Like } from "../math/vec";
2
2
  export type MeshKind = "triangles" | "vertices" | "edges";
3
+ /** A geometry file — what `lecodes assets geometry <model.glb>` writes (`<model>.geometry.json`):
4
+ * one merged, world-space triangle buffer. `Geometry.load(asset('./casing.geometry.json'))`. */
5
+ export type GeometryData = {
6
+ format?: "lecodes-geometry";
7
+ version?: number;
8
+ /** xyz per vertex. */
9
+ vertices: ArrayLike<number>;
10
+ /** xyz per vertex (unit length). */
11
+ normals: ArrayLike<number>;
12
+ /** uv per vertex. */
13
+ uv: ArrayLike<number>;
14
+ /** Triangle list, ≤ 65535 vertices addressed. */
15
+ indices: ArrayLike<number>;
16
+ };
3
17
  export declare class Geometry {
4
18
  vertices: Float32Array;
5
19
  normals: Float32Array;
@@ -25,13 +39,35 @@ export declare class Geometry {
25
39
  static box(size?: Vec3Like | number): Geometry;
26
40
  static sphere(options?: SphereOptions): Geometry;
27
41
  static cylinder(options?: CylinderOptions): Geometry;
42
+ /** A capsule along Y, centred at the origin: a cylinder `length` long (between the two cap centres)
43
+ * with a hemisphere of `radius` on each end — one closed surface, smooth across the seams. */
44
+ static capsule(options?: CapsuleOptions): Geometry;
28
45
  static plane(options?: PlaneOptions): Geometry;
46
+ /** A Geometry from a geometry file's contents (`GeometryData` — see `Geometry.load`). The arrays
47
+ * are copied into typed buffers, so the source object can be dropped. */
48
+ static fromData(data: GeometryData): Geometry;
49
+ /** Load a geometry file: `lecodes assets geometry casing.glb` → `casing.geometry.json` →
50
+ * `await Geometry.load(asset('./casing.geometry.json'))`. A GLB's triangles as a plain buffer,
51
+ * for `Particles({ mesh })` debris (shell casings, rubble) and `Mesh.from`.
52
+ * A `.json` project file is INLINED by the compiler — `asset('./x.json')` is the parsed data, not
53
+ * a URL — so that form (and `import data from './x.json'`) is read directly, no fetch; a string
54
+ * (a URL, a non-`.json` asset such as `.geo`) is fetched. */
55
+ static load(source: string | GeometryData): Promise<Geometry>;
29
56
  }
30
57
  export type SphereOptions = {
31
58
  radius?: number;
32
59
  widthSegments?: number;
33
60
  heightSegments?: number;
34
61
  };
62
+ export type CapsuleOptions = {
63
+ radius?: number;
64
+ /** Distance between the two cap centres (the straight part); 0 = a sphere. Default 1. */
65
+ length?: number;
66
+ /** Segments around the axis. */
67
+ widthSegments?: number;
68
+ /** Rings per hemisphere (pole to equator). */
69
+ capSegments?: number;
70
+ };
35
71
  export type CylinderOptions = {
36
72
  radius?: number;
37
73
  radiusTop?: number;
@@ -1,6 +1,13 @@
1
1
  import { type ColorInput } from "../core/color";
2
2
  import type { Vec3Like } from "../math/vec";
3
- import { Node } from "./Node";
3
+ import { Node, type NodeTweenProps } from "./Node";
4
+ import { type TweenMeta } from "../animate/tween/spec";
5
+ import type { Animation } from "../animate/tween/Animation";
6
+ /** Animatable light props on top of the node transform. */
7
+ export type LightTweenProps = NodeTweenProps & {
8
+ intensity?: number | number[];
9
+ color?: ColorInput | ColorInput[];
10
+ };
4
11
  export type SunOptions = {
5
12
  /** Light direction (points where the light travels). Defaults to a typical key-light angle. */
6
13
  direction?: Vec3Like;
@@ -41,17 +48,27 @@ export type PointOptions = {
41
48
  /** Point-light shadows are a cubemap render per light; off by default. */
42
49
  castShadows?: boolean;
43
50
  /**
44
- * Whether `lecodes lightmap bake` bakes this light into the level's lightmap (docs/lightmap-plan.md).
45
- * A baked light is switched OFF at runtime once the bake applies — statics read it from the atlas,
46
- * movers from the light volume — so it costs nothing per frame; a light that must stay live (a
47
- * flicker, a lamp the player can shoot out) says `baked: false` and is kept out of the bake.
48
- * Lights created after the level loads (a muzzle flash) are never in a bake. Default true.
51
+ * Whether `lecodes lightmap bake` bakes this light into the level's lightmap. A baked lamp lights the statics
52
+ * from the atlas and the MOVERS from the level's light grid, and is held dark in real time while that grid is
53
+ * loaded (without a grid it keeps lighting the movers live); a light
54
+ * that must reach the statics live too — a flicker, a lamp the player can shoot out, a muzzle flash —
55
+ * says `baked: false`: it is kept out of the bake and put on the statics' light channel. Default true.
49
56
  */
50
57
  baked?: boolean;
58
+ /**
59
+ * THE BAKE'S SHAPE of this lamp: `[width, height]` in metres = an AREA light - a rectangle in the light's local XZ
60
+ * plane, emitting along its local -Y (down, for an unrotated node) with the same lumens: a ceiling panel. The bake
61
+ * lights from the whole rectangle (soft shadows, light from where the panel is and not from a point inside the
62
+ * fixture). Real time it is still the point light above. Ignored with `baked: false`.
63
+ */
64
+ bakeArea?: readonly [number, number];
51
65
  };
52
66
  export declare class Light extends Node {
53
67
  private _intensity;
54
- /** The most recently created sun — what `Lightmap.load` uses unless told otherwise. */
68
+ /** Tween `intensity` / `color` (a flash, a sunrise) and the transform — see {@link Node.animateTo}. */
69
+ animateTo(props: LightTweenProps & TweenMeta): Animation;
70
+ animateFrom(props: LightTweenProps & TweenMeta): Animation;
71
+ /** The most recently created sun. */
55
72
  static lastSun: Light | null;
56
73
  _shadowDistance: number;
57
74
  /** A directional sun light. */
@@ -69,6 +86,7 @@ export declare class Light extends Node {
69
86
  /** Live intensity (sun: lux, point: lumens) — animate a flash without rebuilding the light. */
70
87
  get intensity(): number;
71
88
  set intensity(value: number);
89
+ _holdDark(dark: boolean): void;
72
90
  destroy(): void;
73
91
  set color(value: ColorInput);
74
92
  get color(): ColorInput;
@@ -1,46 +1,34 @@
1
1
  import { Node } from "./Node";
2
2
  import type { Scene } from "./Scene";
3
3
  export type LightmapLoadOptions = {
4
- /** 1 = baked sun shadows at full strength, 0 = ambient occlusion only. Default 1. */
5
- sunStrength?: number;
6
- /** Multiplier on the ambient (IBL) share in the shadow math — how bright a fully shadowed texel
7
- * stays. 1 reproduces filament's own real-time shadow darkness (the shader reads the scene's sun
8
- * and IBL from filament's per-frame uniforms); raise it for lighter shadows, lower for deeper. Default 1. */
9
- ambientScale?: number;
10
- /** How much of the baked ambient occlusion applies: 1 = all of it, 0 = none. An interior lit by
11
- * its ambient probe alone can want less than the geometric truth. Default 1. */
12
- aoStrength?: number;
13
- /** Multiplier on the baked point lights' irradiance (default 1). Purely ARTISTIC: the bake is
14
- * physical (verified against the analytic 1/d^2 sum over the level's lamps - where line of sight
15
- * is clear the two agree within ~17 %), and a room lit to its real illuminance simply reads dim
16
- * against an ambient fill. This dials the lamps up without a re-bake, since their irradiance is
17
- * IN the atlas and `--lamp-gain` would cost a full one.
18
- * It scales the atlas AND the light volume together, so statics and movers stay consistent - but
19
- * note they are NOT equally forgiving: a static surface carries the bake's own cosine and
20
- * shadowing, while a mover reads the volume through a flat half-cosine, so a large boost blows
21
- * movers out long before it blows out the level. Past ~x100 expect to light the movers
22
- * separately. */
23
- lightBoost?: number;
24
- /** Multiplier on the light VOLUME's irradiance alone — the movers' own `lightBoost`. Default =
25
- * `lightBoost`, and since 2026-08-31 that default is the right one: the volume is an ambient cube
26
- * (irradiance per axis face, blended by the pixel's normal), so a mover's top takes a lamp above it
27
- * and its underside does not — the same shading the atlas gives the statics. Before that the volume
28
- * was one flat 0.5·E on every face and this knob was the workaround. Kept as a trim. */
29
- volumeBoost?: number;
4
+ /** A DEBUG multiplier on the atlas' lux (default 1): the atlas is physical, so this is a knob for looking,
5
+ * not a look. */
6
+ lightScale?: number;
7
+ /** The level's own radiance of emission 1.0 for the BAKE (cd / m², `BakeConfig.emissiveNits`): an imported pack's
8
+ * emissive values are tuned for a look, not for light - this is where the level says how much its panels really give.
9
+ * The CLI's `--emissive-nits` wins over it; without either the camera's exposure decides. */
10
+ emissiveNits?: number;
11
+ /** `false` = the emissive surfaces light NOTHING in the bake (the level is lit by its lamps, the panels are decoration)
12
+ * while `emissiveNits` still says how bright their glow is drawn. Default true. */
13
+ emissiveBake?: boolean;
14
+ /** FOR THE BAKE: materials that let light THROUGH them, by their glTF name (`"name*"` = every name with that start):
15
+ * the share of a shadow ray that passes, tinted by the material's base colour x map - an awning's warm, patterned light
16
+ * on the sand. The surface itself stays opaque: baked, drawn as before, a real-time shadow caster.
17
+ * A number = the share that goes STRAIGHT through (the gaps of a weave: it draws the map's picture on the ground);
18
+ * `{ through, diffuse }` adds the share the fibres SCATTER - the cloth's underside glows like a matte panel and lights
19
+ * what is under it by the solid angle it fills, with no picture. Dense canvas: `{ through: 0.08, diffuse: 0.25 }`. */
20
+ transmit?: Record<string, LightmapTransmit>;
21
+ };
22
+ /** one `transmit` entry (LightmapLoadOptions.transmit): the straight share, or both shares */
23
+ export type LightmapTransmit = number | {
24
+ through?: number;
25
+ diffuse?: number;
30
26
  };
31
27
  export type LightmapInfo = {
32
28
  size: number;
33
29
  texel: number;
34
- /** Atlas pages the bake took (`texture` lists them in order). */
30
+ /** Atlas pages the bake took (`light` / `aux` list them in order). */
35
31
  pages: number;
36
- /** The light volume, when one was loaded: grid dims + cell size in metres. */
37
- volume?: {
38
- dims: number[];
39
- cell: number;
40
- };
41
- /** Point lights the bake carries and this load switched off (statics read them from the atlas,
42
- * movers from the volume); 0 without a `light` atlas. */
43
- lights: number;
44
32
  /** keys applied / keys in the file / registered statics without a rect */
45
33
  applied: number;
46
34
  total: number;
@@ -48,38 +36,89 @@ export type LightmapInfo = {
48
36
  };
49
37
  export type LightmapFiles = {
50
38
  data: string;
51
- /** The atlas — one page, or every page in order for a bake that took several. */
52
- texture: string | string[];
53
- /** The point lights' baked irradiance (pages like `texture`). */
54
- light?: string | string[];
39
+ /** The irradiance atlas — one page, or every page in order for a bake that took several. */
40
+ light: string | string[];
41
+ /** The aux atlas (sun / sky visibility + light direction), pages like `light`. */
42
+ aux: string | string[];
43
+ /** DEBUG: the direct-light-only pages a `--split` bake wrote (`<stem>-direct[_n].ktx2`), for the "direct" view. */
44
+ direct?: string | string[];
45
+ /** DEBUG: the bounce-only pages of a `--split` bake (`<stem>-indirect[_n].ktx2`), for the "indirect" view. */
46
+ indirect?: string | string[];
47
+ /** The reflection probes (`<stem>-probes.ktx2`), when the bake placed some (`lecodes lightmap bake --probe-spacing`). */
48
+ probes?: string;
49
+ /** THE LIGHT GRID for movers (`<stem>.lgrid`, `lecodes lightmap bake --volume`): the baked ambient light of every
50
+ * place a mover can be. Absent = movers keep the scene's IBL. */
55
51
  volume?: string;
56
52
  };
53
+ /** The lightmap material's data views (`Lightmap.debug`); "direct" / "indirect" bind a `--split` bake's pages instead. */
54
+ export type LightmapDebugMode = "off" | "irradiance" | "albedo" | "normal" | "shadingNormal" | "share" | "skyVis" | "atlas" | "direction" | "lux" | "relief" | "direct" | "indirect" | "probe" | "probeMap" | "skyPath" | "probePath";
55
+ export type LightmapDebugParams = {
56
+ /** `lux`: the false-colour ramp's log10 range (default 0..5, i.e. 1 lux .. 100 000). */
57
+ luxRange?: [number, number];
58
+ };
59
+ /** What `Lightmap.probe` answers: the baked texel under a screen point. */
60
+ export type LightmapProbe = {
61
+ key: string;
62
+ entity: number;
63
+ node: string;
64
+ material: string;
65
+ page: number;
66
+ /** The texel's column / row on its page. */
67
+ texel: [number, number];
68
+ uv1: [number, number];
69
+ world: [number, number, number];
70
+ distance: number;
71
+ };
57
72
  export declare class Lightmap {
58
73
  /** True while `lecodes lightmap bake` runs the app — skip menus and build the scene straight away. */
59
74
  static get baking(): boolean;
60
75
  private static entries;
61
- private static dynamics;
62
76
  private static ordinals;
63
77
  private static warned;
64
- private static switchedOff;
78
+ /** The bake bound last (`load`): what `probe` reads rects from. */
79
+ private static bound;
65
80
  /** Register static geometry — a Model, a Mesh, or any node whose subtree holds them. Statics are
66
- * both receivers and occluders in the bake. `key` names the entry in lightmap.bake (default:
81
+ * both receivers and occluders in the bake. `key` names the entry in the .bake (default:
67
82
  * the node's name + a running number, `container#3`); pass one when names are not stable. */
68
83
  static add(node: Node, key?: string): Node;
69
84
  private static next;
70
- /** Forget every registration (a scene rebuild); baked-off lights come back to the real-time path. */
85
+ private static movers;
86
+ private static volumeOn;
87
+ /** A MOVER takes its ambient light from the level's baked LIGHT GRID (`env.lightmap.volume`) at the place it is at,
88
+ * every frame — whatever its shader: the standard glTF one or a custom lit material. Every Model / Mesh under `node`
89
+ * is marked (a character, a weapon with its parts). A scene file does this itself for every node a Physics aspect or
90
+ * a CharacterController moves; call it for what CODE spawns — an enemy, a pickup, a projectile. `on = false` hands
91
+ * the subtree back to the scene's IBL. Safe before the level's bake has loaded. */
92
+ static track(node: Node, on?: boolean): void;
93
+ private static loadVolume;
94
+ /** Forget every registration (a scene rebuild). */
71
95
  static clear(): void;
72
96
  /** Apply a bake — or, under `lecodes lightmap bake`, run it. Resolves to null when nothing was
73
97
  * applied (no bake yet, a host without the feature, bake mode). */
98
+ private static scene;
74
99
  static load(scene: Scene, files: LightmapFiles, options?: LightmapLoadOptions): Promise<LightmapInfo | null>;
75
- /** Every live point light within 5 cm of a baked one is held dark (a moved lamp keeps lighting live
76
- * — and double, until the next bake, which is the honest state of a stale bake). */
77
- private static switchOffBaked;
78
- /** lightmap.volume → the engine's 3D textures → THE volume for every dynamic lightmap-material instance,
79
- * plus the registered dynamic Meshes. Null when the file is a placeholder or the host lacks the feature. */
80
- private static loadVolume;
81
- /** A Mesh keeps its look (colour / map / roughness / metallic) but moves to the lightmap material. */
100
+ /** The bake's `<key>#k` instances by their base key: the parts of a GLB unwrapped in groups (k = extras.lightmapGroup). */
101
+ private static groupsOf;
102
+ /** Switch every lightmapped surface to a DATA view (or back with `"off"` / 0). Level-wide, instant —
103
+ * except "direct" / "indirect", which load that page set the first time (a `--split` bake listed in the scene). */
104
+ static debug(mode: LightmapDebugMode | number, params?: LightmapDebugParams): void;
105
+ /** Bind another page set of the bound bake to every surface (the "direct" / "indirect" views). */
106
+ private static show;
107
+ private static balls;
108
+ /** DEBUG: a mirror ball at every reflection probe of the bound bake (`on`), or none (`off`) — a ball reflects the
109
+ * probes around its point unoccluded (at a probe's own position that is the probe itself), so a probe inside a
110
+ * wall, a black one or a leak across a wall shows at a glance. Metallic, roughness 0; view "probe" shows the
111
+ * probes alone. */
112
+ static debugProbes(on: boolean, radius?: number): number;
113
+ /** The view names in `lightmapDebug`'s order — for a knob that cycles them. */
114
+ static get debugModes(): readonly LightmapDebugMode[];
115
+ /** The baked texel under a screen point (logical px): its instance key, page and texel column / row,
116
+ * so the value can be looked up in the bake's debug layers (`lecodes lightmap inspect`). null when
117
+ * nothing baked is under the point or the host has no triangle pick. */
118
+ static probe(screenX: number, screenY: number): LightmapProbe | null;
119
+ /** A hex colour as the 9-char form the float4 uniform path expects ("#rrggbbaa"). */
120
+ private static hex8;
121
+ /** The lightmap material for a static Mesh, from whatever lit material it carried (colour, map, roughness, metallic). */
82
122
  private static meshMaterial;
83
- private static swapMeshMaterial;
84
123
  private static bake;
85
124
  }
@@ -119,8 +119,6 @@ export type LitMaterialOptions = MaterialColorOptions & MaterialStateOptions & {
119
119
  /** Metallic factor, 0 (dielectric) … 1 (metal). Unset = the shader's default. */
120
120
  metallic?: number;
121
121
  };
122
- /** `Material.lightmapShading` tiers — see the setter. */
123
- export type LightmapShading = "full" | "baked" | "baked-lite";
124
122
  export declare class Material {
125
123
  readonly shader: FetchResponse | "unknown";
126
124
  readonly uniforms: Record<string, UniformValue>;
@@ -153,29 +151,43 @@ export declare class Material {
153
151
  static decal(options?: DecalMaterialOptions): Material;
154
152
  /** Material that samples a VideoPlayer's texture. */
155
153
  static video(map?: Texture): Material;
156
- /** The lightmap material (docs/lightmap-plan.md): PBR base colour × a baked shadow/AO atlas on UV1.
157
- * Models take it through `Model.load(…, { lightmap: true })`; `Lightmap.load` builds one per static
158
- * Mesh. Parameters use gltfio's names (`baseColorFactor`, `baseColorMap`, `roughnessFactor`,
159
- * `metallicFactor`) plus `lightmap`, `lightmapST`, `ambientScale`, `sunStrength` (the sun / IBL terms come from
160
- * filament's per-frame uniforms inside the shader). */
154
+ /** The lightmap material (packages/creator-bake): a lit PBR material whose whole DIFFUSE light is the
155
+ * baked irradiance atlas on UV1 (`lightmapLight`, lux) — the real-time sun, lamps and diffuse IBL do not
156
+ * touch it; the specular IBL stays, occluded by the baked sky visibility. Models take it through
157
+ * `Model.load(…, { lightmap: true })`; a static Mesh gets one from here. Parameters use gltfio's names
158
+ * (`baseColorFactor`, `baseColorMap`, `roughnessFactor`, `metallicFactor`) plus `lightmapLight`, `lightmapAux`,
159
+ * `lightmapST`, `lightScale` (the level-wide value `Lightmap.load` sets) and the debug view knobs. */
161
160
  static lightmap(): Material;
161
+ /** `Material.lightmap()`'s masked twin (`blending: masked`, same shader and parameters): what the engine
162
+ * gives a lightmapped model's alpha-MASK materials. The cutoff comes from the glTF material. */
163
+ static lightmapMasked(): Material;
162
164
  /** The terrain splat material (docs/terrain-plan.md §1.5): four albedo (+ normal-map) layers blended by
163
165
  * a control map on the terrain's own grid, per-layer `tiling` (metres per repeat) / `roughness` /
164
- * `normalScale` / `triplanar`, lightmap-aware (the same `lightmap` / `lightmapST` block as
165
- * `Material.lightmap`). `Terrain` builds and owns one per terrain; the uniforms are primed here so an
166
- * unset layer is white and an unbaked terrain is fully lit. */
166
+ * `normalScale` / `triplanar`. `Terrain` builds and owns one per terrain; the uniforms are primed here so an
167
+ * unset layer is white. Lit real-time; a BAKED terrain takes `Material.terrainLightmap()`. */
167
168
  static terrain(): Material;
169
+ /** `Material.terrain()` for a BAKED terrain (terrain-lightmap.mat): the same layers and uniforms, shaded like
170
+ * `Material.lightmap()` — the whole diffuse light is the baked atlas on the terrain's UV1 (one rect), which
171
+ * `Lightmap.load` binds. A scene file's `terrain:` node that is a lightmap static is built with it; without a
172
+ * bound bake it draws black, like any lightmapped static. No reflection probes (the sampler budget): it reflects the
173
+ * sky through the baked sky visibility. */
174
+ static terrainLightmap(): Material;
168
175
  private static _lmTemplate;
169
- static _lightmapShading: LightmapShading;
170
- /** Which shader lightmapped models take — a graphics-quality tier, engine-wide. `"full"` is filament's
171
- * lit path over the baked atlas (IBL specular, real-time point lights such as a muzzle flash, sun
172
- * shadows on dynamic objects). `"baked"` keeps the baked light and an approximated ambient but drops
173
- * the lit path: ~40 % cheaper per pixel on a fill-bound GPU. `"baked-lite"` is that minus the normal,
174
- * metallic/roughness and occlusion map reads (the factors stand in, the atlas keeps the baked AO;
175
- * emissive still glows): +20 % more at 1080p on the same GPU. Read when a lightmapped model LOADS, so set
176
- * it before the level (a settings menu applies it on the next level load), like `Texture.maxSize`. */
177
- static get lightmapShading(): LightmapShading;
178
- static set lightmapShading(mode: LightmapShading);
176
+ private static _lmMaskedTemplate;
177
+ /** The FOLIAGE tier (`Model.load(…, { foliage: true })`, see `Foliage`): an unlit shader with its own cheap
178
+ * lighting plus a vertex shader that sways in the wind, bends away from benders and thins out with distance,
179
+ * and a per-copy tint. glTF parameters like `Material.lightmap()` plus `wind` / `sway` / `fade` / `benders`,
180
+ * which the engine writes itself. Not baked by the rewritten bake yet. */
181
+ static foliage(): Material;
182
+ /** `Material.foliage()`'s masked twin (`blending: masked`): what the engine gives a foliage model's alpha-MASK
183
+ * materials — the cards. */
184
+ static foliageMasked(): Material;
185
+ /** the glTF-side identity values every provider-handed material starts from */
186
+ private static _glbDefaults;
187
+ private static _lightmapDefaults;
188
+ private static _folTemplate;
189
+ static _foliageMaskedTemplate(): Material;
190
+ private static _folMaskedTemplate;
179
191
  /** Shadow-catcher material (transparent except where shadows fall). */
180
192
  static shadow(color?: ColorInput): Material;
181
193
  /** Load a custom compiled shader (.mat URL) as a material. */
@@ -1,5 +1,5 @@
1
1
  import { type Vec3Like } from "../math/vec";
2
- import { Geometry, type CylinderOptions, type PlaneOptions, type SphereOptions } from "./Geometry";
2
+ import { Geometry, type CapsuleOptions, type CylinderOptions, type PlaneOptions, type SphereOptions } from "./Geometry";
3
3
  import { Material } from "./Material";
4
4
  import { Node } from "./Node";
5
5
  /** Common transform/render options every primitive factory accepts. */
@@ -21,6 +21,10 @@ export declare class Mesh extends Node {
21
21
  /** Replace the geometry in place — the node, its transform and its material stay, the vertex and
22
22
  * index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
23
23
  setGeometry(geometry: Geometry): this;
24
+ private _renderPriority?;
25
+ private _culling?;
26
+ private _castShadows?;
27
+ private _receiveShadows?;
24
28
  /** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
25
29
  get material(): Material;
26
30
  set material(m: Material);
@@ -38,6 +42,8 @@ export declare class Mesh extends Node {
38
42
  }): Mesh;
39
43
  static sphere(options?: MeshOptions & SphereOptions): Mesh;
40
44
  static cylinder(options?: MeshOptions & CylinderOptions): Mesh;
45
+ /** A capsule along Y (`radius`, `length` between the cap centres) — a character's or a collider's shape in one mesh. */
46
+ static capsule(options?: MeshOptions & CapsuleOptions): Mesh;
41
47
  static plane(options?: MeshOptions & PlaneOptions): Mesh;
42
48
  /** Wrap a custom Geometry. */
43
49
  static from(geometry: Geometry, options?: MeshOptions): Mesh;
@@ -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. */
@@ -43,11 +77,13 @@ export declare class Model extends Node {
43
77
  loaded: number;
44
78
  total?: number;
45
79
  }) => void;
46
- /** Baked lighting (docs/lightmap-plan.md). `true` = a STATIC: loads through the lightmap material so
80
+ /** Baked lighting (packages/creator-bake). `true` = a STATIC: loads through the lightmap material so
47
81
  * `Lightmap.load` can bind its atlas rect (the GLB needs TEXCOORD_1 — `lecodes assets doctor
48
- * --lightmap-uv`). `'dynamic'` = a mover: the same material, lit by the level's light VOLUME instead
49
- * (no UV1 needed). Omitted: `'dynamic'` while a scene file with `env.lightmap.volume` is running,
50
- * else off. `false` = the standard shader. Hosts without lightmap support ignore it. */
51
- lightmap?: boolean | "dynamic";
82
+ * --lightmap-uv`) and takes nothing from the real-time lights once the bake applies. Omitted / `false` =
83
+ * the standard shader, lit real-time (movers). Hosts without lightmap support ignore it. */
84
+ lightmap?: boolean;
85
+ /** Vegetation: load through the FOLIAGE tier (see `Foliage`) — wind, touch bending, distance fade,
86
+ * per-copy tint. Hosts without the tier fall back to the standard shader. */
87
+ foliage?: boolean;
52
88
  }): Promise<Model>;
53
89
  }
@@ -7,6 +7,17 @@ import type { Geometry } from "./Geometry";
7
7
  import { Material } from "./Material";
8
8
  import type { CompAxis, CompWriter } from "../core/compWrite";
9
9
  import type { ClickEvent, TouchStartEvent } from "../runtime/touch";
10
+ import type { Animation } from "../animate/tween/Animation";
11
+ import { type TweenMeta } from "../animate/tween/spec";
12
+ /** Animatable transform props of a 3D node: a value tweens from the current one, an ARRAY OF
13
+ * VALUES is a keyframe list (`position: [[0,0,0], [0,2,0]]`). */
14
+ export type NodeTweenProps = {
15
+ position?: Vec3Like | Vec3Like[];
16
+ scale?: number | Vec3Like | (number | Vec3Like)[];
17
+ quaternion?: QuatLike | QuatLike[];
18
+ /** Degrees, YXZ; interpolated per axis without shortest-arc, so `[0, 720, 0]` spins twice. */
19
+ eulerAngles?: Vec3Like | Vec3Like[];
20
+ };
10
21
  /** id → Node, so host callbacks (touch hits, animation events) route back to the owning object. */
11
22
  export declare const nodeRegistry: Registry<Node>;
12
23
  export type NodeEvents = {
@@ -59,6 +70,13 @@ export declare class Node extends AspectHost<NodeEvents> implements CompWriter {
59
70
  set scale(v: Vec3Like | number);
60
71
  get quaternion(): Quat;
61
72
  set quaternion(v: QuatLike);
73
+ /** Tween the transform to the given values — `node.animateTo({ position: [0, 2, 0], duration: 800,
74
+ * easing: 'inOutCubic' })`; arrays of values are keyframes. Runs on the game clock (pauses with
75
+ * the game) unless `clock: 'ui'`. Returns the {@link Animation} handle. Phase 1: plain nodes —
76
+ * a physics-owned node (a body / character) is not routed through its engine object yet. */
77
+ animateTo(props: NodeTweenProps & TweenMeta): Animation;
78
+ /** Tween FROM the given values to the node's current transform (an entrance). */
79
+ animateFrom(props: NodeTweenProps & TweenMeta): Animation;
62
80
  get eulerAngles(): Vec3;
63
81
  set eulerAngles(v: Vec3Like);
64
82
  get forward(): Vec3;