@driftengine/core 4.7.3 → 4.7.4

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.
package/CHANGELOG.json CHANGED
@@ -1,4 +1,30 @@
1
1
  [
2
+ {
3
+ "version": "4.7.4",
4
+ "date": "2026-10-02",
5
+ "entries": [
6
+ {
7
+ "kind": "fixed",
8
+ "short": "A component's declared field values fill every field an entity is not given: an add with some fields, a prefab that leaves one out, a script's instantiate, a save from before the field.",
9
+ "text": "**`driftscript` moves to 1.14.0**, whose compiler now carries the value each component field was declared with (`seed: u32 = 7`). Before, the value was parsed and dropped, so every entity started at zero and nothing said so. A component type has `defaults`, and `defineComponent(schema, defaults)` refuses one that names no field or does not fit its column. The store fills an omitted field from them, so `world.add` with some of the fields, a prefab that leaves one out and a script's `ecs.instantiate` all start at the declared values; a `null` given for an optional field is still its absence. `deserializeWorld` gives a field the saved scene does not have its declared value, so a save loads across an added field with what the component says. DriftScript 1.14.0 also type-checks a component's defaults, which nothing did before, and refuses one that is computed (DS0275)."
10
+ },
11
+ {
12
+ "kind": "added",
13
+ "short": "The manual gains XR, and a Systems section that opens with saves and preferences, each chapter built on an example that runs live and keeps its behaviour in DriftScript.",
14
+ "text": "**XR** covers asking a browser what it can present, entering a session, and reading the head, the controllers and a tracked hand, from TypeScript and from `drift/xr`, through the simulated runtime the package tests against. It says plainly that the renderer cannot draw into a headset yet, and the xr README's sample, which called `renderer.setViewport` and `renderer.draw`, now stops where the renderer does. **Saves and preferences** covers the synchronous store and why it is synchronous, preferences checked field by field, `RemoteSaveStore` over a backend that answers later, `serializeWorld` and `deserializeWorld` with a save read back across an added field, and `drift/persistence`, around a garden bed that saves to a server the page can make slow or take offline."
15
+ },
16
+ {
17
+ "kind": "fixed",
18
+ "short": "Four examples behave on any machine: the vehicle starts clear of the cones, readouts fit a small frame, chemistry holds 60 fps, and DriftLight starts smoothly on a slow CPU.",
19
+ "text": "The vehicle spawned overlapping a cone and came to rest on it with two wheels in the air; it starts on the ramp's line. A readout is sized to the frame it is in, so the manual's 768 by 432 frames no longer cut lines off, and it can keep clear of a panel. Chemistry stepped a quarter second sixty times a second and drew 3 fps once smoke filled the air; it steps a sixteenth, about four times the clock, at 60 fps, and `stage.run` takes loop options so a slow frame catches up two ticks at most. DriftLight baked 64 bricks a frame and ran at 1 to 8 fps for its first ten seconds on a CPU three times slower; it bakes for four milliseconds a frame and stays above 18. Under that throttling 44 of the 46 examples hold 57 to 60 fps, and chemistry 16."
20
+ },
21
+ {
22
+ "kind": "changed",
23
+ "short": "The repository lints without a warning: oxlint's 170 are fixed, one of them a test that divided its fallback where it meant to check a length.",
24
+ "text": "Unused imports, constants, functions, variables and parameters are gone, products by zero are written as the terms that remain, and optional chains in tests stay optional to the end. Inside the native host, the window, the document and the canvas take their `on<type>` attributes from a base class that declares them, in place of an interface merged into each class, and fdlibm's constants are spelled with the digits a double holds, each parsing to the same value."
25
+ }
26
+ ]
27
+ },
2
28
  {
3
29
  "version": "4.7.3",
4
30
  "date": "2026-10-02",
@@ -812,8 +812,9 @@ export class MeshBuilder {
812
812
  // later one is transported from it, so the choice never shows.
813
813
  const ax = Math.abs(tx) < 0.9 ? 1 : 0;
814
814
  const ay = Math.abs(tx) < 0.9 ? 0 : 1;
815
- ux = ay * tz - 0 * ty;
816
- uy = 0 * tx - ax * tz;
815
+ // (ax, ay, 0) x tangent, with the terms the zero removes left out.
816
+ ux = ay * tz;
817
+ uy = -ax * tz;
817
818
  uz = ax * ty - ay * tx;
818
819
  seeded = true;
819
820
  }
@@ -66,14 +66,14 @@ const HALF_PI = 1.5707963267948966;
66
66
  const PI = 3.141592653589793;
67
67
  /* fdlibm's `__kernel_sin` coefficients. */
68
68
  const S1 = -1.6666666666666632e-1;
69
- const S2 = 8.3333333333224894e-3;
69
+ const S2 = 8.33333333332249e-3;
70
70
  const S3 = -1.984126982985795e-4;
71
71
  const S4 = 2.7557313707070068e-6;
72
72
  const S5 = -2.5050760253406863e-8;
73
73
  const S6 = 1.5896909952115501e-10;
74
74
  /* fdlibm's `__kernel_cos` coefficients. */
75
75
  const C1 = 4.1666666666666602e-2;
76
- const C2 = -1.3888888888874109e-3;
76
+ const C2 = -1.388888888887411e-3;
77
77
  const C3 = 2.480158728947673e-5;
78
78
  const C4 = -2.7557314351390663e-7;
79
79
  const C5 = 2.0875723212981748e-9;
@@ -273,7 +273,7 @@ export function exactLog(x) {
273
273
  /* |f| < 2^-20: two terms of the series are the whole answer. */
274
274
  if (f === 0)
275
275
  return k === 0 ? 0 : k * LN2_HI + k * LN2_LO;
276
- const r = f * f * (0.5 - 0.33333333333333333 * f);
276
+ const r = f * f * (0.5 - 0.3333333333333333 * f);
277
277
  return k === 0 ? f - r : k * LN2_HI - (r - k * LN2_LO - f);
278
278
  }
279
279
  const s = f / (2 + f);
@@ -23,8 +23,6 @@ import { validateMeshData } from '@driftengine/drft';
23
23
  * well. Both halves are fixed — `flat.ts` normalises the lobe, and this is the width that
24
24
  * actually matches.
25
25
  */
26
- /* The engine's fixed lobe, stated once in `vertexDefaults.ts` so both backends read one number. */
27
- const DEFAULT_ROUGHNESS = ABSENT_ATTRIBUTE['roughness']?.[0] ?? 0.4277;
28
26
  /** Attribute locations are fixed engine-wide (`layout(location = n)` in GLSL). */
29
27
  const ATTR_POSITION = 0;
30
28
  const ATTR_NORMAL = 1;
@@ -3,37 +3,8 @@ import { compileProgram, uniformLocations } from './shader.js';
3
3
  import { bindAtmosphere } from './atmosphere.js';
4
4
  import { plumeShaders } from './plumeMaterial.js';
5
5
  import { buildPlumeGeometry } from './plumeGeometry.js';
6
- /**
7
- * Blades in each plume's cross, and the vertices that costs.
8
- *
9
- * **Two, crossed at 90°, instead of one card turned to face the viewer.** A single
10
- * billboard has no three-dimensional structure to see: orbiting a fire showed the
11
- * identical silhouette from every angle, because every card shared one camera-derived
12
- * axis and the whole plume swivelled as a sheet. Reported twice: the fire did not read
13
- * as three-dimensional, it read as something that moved with the camera — and after a
14
- * first attempt that only *leaned* edge-on cards toward the viewer, it still did,
15
- * correctly, because leaning still rotates whichever cards are edge-on, so the ensemble
16
- * still tracks the viewer.
17
- *
18
- * A cross cannot track anything: both blades are fixed in world space, so turning
19
- * around a fire genuinely brings one broadside as the other goes edge-on, which is
20
- * the parallax a volume has. It also removes the reason billboarding existed — a
21
- * blade going edge-on no longer vanishes, because its partner is square-on at that
22
- * exact moment.
23
- *
24
- * Two rather than three because the plumes blend additively and are drawn in
25
- * quantity: the third blade costs 50% more geometry for a difference the eye does not
26
- * separate at the sizes these are drawn.
27
- */
28
- const BLADES = 2;
29
6
  /* The shared count, so both backends step a plume's vertices the same way. */
30
7
  const VERTS_PER_PLUME = PLUME_VERTS;
31
- const CORNERS = [
32
- [-1, -1],
33
- [1, -1],
34
- [1, 1],
35
- [-1, 1],
36
- ];
37
8
  export class PlumeRenderer {
38
9
  program;
39
10
  uniforms;
@@ -122,7 +122,7 @@ export class SceneNode {
122
122
  attachChild(child) {
123
123
  if (child === this)
124
124
  throw new Error('SceneNode: a node cannot be its own child (cycle)');
125
- for (let at = this; at !== null; at = at.parent) {
125
+ for (let at = this.parent; at !== null; at = at.parent) {
126
126
  if (at === child)
127
127
  throw new Error('SceneNode: that would make a cycle');
128
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/core",
3
- "version": "4.7.3",
3
+ "version": "4.7.4",
4
4
  "description": "The runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -29,8 +29,8 @@
29
29
  ],
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
- "@driftengine/drft": "4.7.3",
33
- "@driftengine/physics": "4.7.3",
32
+ "@driftengine/drft": "4.7.4",
33
+ "@driftengine/physics": "4.7.4",
34
34
  "gl-matrix": "^3.4.3"
35
35
  },
36
36
  "author": "Drift Technologies",
@@ -67,8 +67,8 @@
67
67
  "prepack": "node ../../scripts/build.mjs @driftengine/core"
68
68
  },
69
69
  "devDependencies": {
70
- "@driftengine/nav": "4.7.3",
71
- "@driftengine/network": "4.7.3",
72
- "@driftengine/texture": "4.7.3"
70
+ "@driftengine/nav": "4.7.4",
71
+ "@driftengine/network": "4.7.4",
72
+ "@driftengine/texture": "4.7.4"
73
73
  }
74
74
  }
@@ -969,8 +969,9 @@ export class MeshBuilder {
969
969
  // later one is transported from it, so the choice never shows.
970
970
  const ax = Math.abs(tx) < 0.9 ? 1 : 0;
971
971
  const ay = Math.abs(tx) < 0.9 ? 0 : 1;
972
- ux = ay * tz - 0 * ty;
973
- uy = 0 * tx - ax * tz;
972
+ // (ax, ay, 0) x tangent, with the terms the zero removes left out.
973
+ ux = ay * tz;
974
+ uy = -ax * tz;
974
975
  uz = ax * ty - ay * tx;
975
976
  seeded = true;
976
977
  }
package/src/math/exact.ts CHANGED
@@ -69,7 +69,7 @@ const PI = 3.141592653589793;
69
69
 
70
70
  /* fdlibm's `__kernel_sin` coefficients. */
71
71
  const S1 = -1.6666666666666632e-1;
72
- const S2 = 8.3333333333224894e-3;
72
+ const S2 = 8.33333333332249e-3;
73
73
  const S3 = -1.984126982985795e-4;
74
74
  const S4 = 2.7557313707070068e-6;
75
75
  const S5 = -2.5050760253406863e-8;
@@ -77,7 +77,7 @@ const S6 = 1.5896909952115501e-10;
77
77
 
78
78
  /* fdlibm's `__kernel_cos` coefficients. */
79
79
  const C1 = 4.1666666666666602e-2;
80
- const C2 = -1.3888888888874109e-3;
80
+ const C2 = -1.388888888887411e-3;
81
81
  const C3 = 2.480158728947673e-5;
82
82
  const C4 = -2.7557314351390663e-7;
83
83
  const C5 = 2.0875723212981748e-9;
@@ -286,7 +286,7 @@ export function exactLog(x: number): number {
286
286
  if ((0x000fffff & (2 + hx)) < 3) {
287
287
  /* |f| < 2^-20: two terms of the series are the whole answer. */
288
288
  if (f === 0) return k === 0 ? 0 : k * LN2_HI + k * LN2_LO;
289
- const r = f * f * (0.5 - 0.33333333333333333 * f);
289
+ const r = f * f * (0.5 - 0.3333333333333333 * f);
290
290
  return k === 0 ? f - r : k * LN2_HI - (r - k * LN2_LO - f);
291
291
  }
292
292
  const s = f / (2 + f);
@@ -5,7 +5,6 @@ import {
5
5
  LINE_VERT_WGSL,
6
6
  } from '../../shaders/generated/line.wgsl.ts';
7
7
  import { buildSegmentQuads } from '../../segmentQuads.ts';
8
- import type { LineSegments } from '../../linePoints.ts';
9
8
  import type { UniformFields } from './scatterPass.ts';
10
9
  import { DEPTH_FORMAT } from './flatPass.ts';
11
10
  import type { PipelineCache } from './pipelineCache.ts';
@@ -30,8 +30,6 @@ import { validateMeshData } from '@driftengine/drft';
30
30
  * well. Both halves are fixed — `flat.ts` normalises the lobe, and this is the width that
31
31
  * actually matches.
32
32
  */
33
- /* The engine's fixed lobe, stated once in `vertexDefaults.ts` so both backends read one number. */
34
- const DEFAULT_ROUGHNESS = ABSENT_ATTRIBUTE['roughness']?.[0] ?? 0.4277;
35
33
 
36
34
  /** Attribute locations are fixed engine-wide (`layout(location = n)` in GLSL). */
37
35
  const ATTR_POSITION = 0;
@@ -62,39 +62,9 @@ export interface PlumeOptions {
62
62
  tint?: Vec3;
63
63
  }
64
64
 
65
- /**
66
- * Blades in each plume's cross, and the vertices that costs.
67
- *
68
- * **Two, crossed at 90°, instead of one card turned to face the viewer.** A single
69
- * billboard has no three-dimensional structure to see: orbiting a fire showed the
70
- * identical silhouette from every angle, because every card shared one camera-derived
71
- * axis and the whole plume swivelled as a sheet. Reported twice: the fire did not read
72
- * as three-dimensional, it read as something that moved with the camera — and after a
73
- * first attempt that only *leaned* edge-on cards toward the viewer, it still did,
74
- * correctly, because leaning still rotates whichever cards are edge-on, so the ensemble
75
- * still tracks the viewer.
76
- *
77
- * A cross cannot track anything: both blades are fixed in world space, so turning
78
- * around a fire genuinely brings one broadside as the other goes edge-on, which is
79
- * the parallax a volume has. It also removes the reason billboarding existed — a
80
- * blade going edge-on no longer vanishes, because its partner is square-on at that
81
- * exact moment.
82
- *
83
- * Two rather than three because the plumes blend additively and are drawn in
84
- * quantity: the third blade costs 50% more geometry for a difference the eye does not
85
- * separate at the sizes these are drawn.
86
- */
87
- const BLADES = 2;
88
65
  /* The shared count, so both backends step a plume's vertices the same way. */
89
66
  const VERTS_PER_PLUME = PLUME_VERTS;
90
67
 
91
- const CORNERS = [
92
- [-1, -1],
93
- [1, -1],
94
- [1, 1],
95
- [-1, 1],
96
- ] as const;
97
-
98
68
  export class PlumeRenderer {
99
69
  private readonly program: WebGLProgram;
100
70
  private readonly uniforms: Record<string, WebGLUniformLocation>;
package/src/scene/node.ts CHANGED
@@ -140,7 +140,7 @@ export class SceneNode {
140
140
  */
141
141
  attachChild(child: SceneNode): void {
142
142
  if (child === this) throw new Error('SceneNode: a node cannot be its own child (cycle)');
143
- for (let at: SceneNode | null = this; at !== null; at = at.parent) {
143
+ for (let at = this.parent; at !== null; at = at.parent) {
144
144
  if (at === child) throw new Error('SceneNode: that would make a cycle');
145
145
  }
146
146
  if (child.parent === this) return;