entangled-worlds-orbiters-shared 2.5.0 → 2.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entangled-worlds-orbiters-shared",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Shared library for entangled worlds orbiters - contains core world rendering, moons system, and UI components",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -42,6 +42,7 @@
42
42
  "files": [
43
43
  "src/**/*",
44
44
  "!src/**/*.test.ts",
45
+ "!src/**/*.test.js",
45
46
  "dist/**/*",
46
47
  "public/**/*",
47
48
  "README.md",
@@ -90,6 +91,7 @@
90
91
  "typecheck": "tsc -p tsconfig.json --noEmit",
91
92
  "test:clock": "node --test --import tsx src/clock/*.test.ts",
92
93
  "test:render": "node --test src/render/*.test.js",
93
- "test": "npm run test:clock && npm run test:render"
94
+ "test": "npm run test:clock && npm run test:render && npm run test:moons",
95
+ "test:moons": "node --test src/moons/*.test.js"
94
96
  }
95
97
  }
@@ -38,7 +38,6 @@ export async function generateMoons({
38
38
  debugShowReferenceMoon = false, // changed from true to prevent extra moon
39
39
  referenceMoonScale = 0.25,
40
40
  // behavior flags
41
- preserveInstanceTint = false, // keep per-instance color even when textured
42
41
  useEmissiveMapFromTexture = true, // reuse color texture as emissiveMap to lift dark sides
43
42
  // The renderer whose KTX2 transcoder decodes the moon texture. Required on any page with more than
44
43
  // one renderer; falls back to the standalone app's global inside the loader.
@@ -118,10 +117,20 @@ export async function generateMoons({
118
117
  }
119
118
  }
120
119
 
121
- const enableVertexColors = !sharedTexture || preserveInstanceTint;
120
+ // `vertexColors` is deliberately NOT set, and setting it here was a bug worth spelling out.
121
+ //
122
+ // The per-moon tint rides on `instanceColor` (set by `setColorAt` below). Three defines
123
+ // USE_INSTANCING_COLOR for that on its own and multiplies it into `vColor` — no material flag
124
+ // needed. `vertexColors` is a DIFFERENT feature: it multiplies by the geometry's `color`
125
+ // ATTRIBUTE. This geometry is a plain SphereGeometry and has no such attribute, so enabling it
126
+ // made the shader multiply by an absent attribute — zero — and every moon rendered BLACK, with the
127
+ // tint annihilated along with it.
128
+ //
129
+ // It only bit the untextured path (`!sharedTexture`), which is the one the performance look uses:
130
+ // moons there carry no texture, so their colour IS the moon, and it was the one case where the
131
+ // colour silently could not work.
122
132
  const moonMaterial = new THREE.MeshStandardMaterial({
123
133
  map: sharedTexture || null,
124
- vertexColors: enableVertexColors,
125
134
  roughness,
126
135
  metalness,
127
136
  emissive: new THREE.Color(emissiveColor),
@@ -1,62 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { createFrameBudget, CAPPED_FPS } from './frameBudget.js';
4
-
5
- test('the first frame is always allowed', () => {
6
- const budget = createFrameBudget();
7
- assert.equal(budget.shouldRender(0), true);
8
- });
9
-
10
- test('a 60Hz display yields 30fps, not 20', () => {
11
- // The whole reason the tolerance exists. rAF on a 60Hz display delivers at ~16.7ms intervals; a
12
- // strict >= 33.3 test rejects the frame at 33.3 and waits for 50, which is 20fps.
13
- const budget = createFrameBudget();
14
- const accepted = [];
15
- for (let i = 0; i < 12; i += 1) {
16
- const now = i * (1000 / 60);
17
- if (budget.shouldRender(now)) accepted.push(i);
18
- }
19
- // Every other frame: 0, 2, 4, ... — half of 60 is the 30 that was asked for.
20
- assert.deepEqual(accepted, [0, 2, 4, 6, 8, 10]);
21
- });
22
-
23
- test('a 120Hz display is capped to the same rate', () => {
24
- const budget = createFrameBudget();
25
- let accepted = 0;
26
- for (let i = 0; i < 120; i += 1) {
27
- if (budget.shouldRender(i * (1000 / 120))) accepted += 1;
28
- }
29
- // One second of 120Hz ticks, capped at 30 — allow the boundary frame either way.
30
- assert.ok(accepted >= CAPPED_FPS && accepted <= CAPPED_FPS + 1, `accepted ${accepted}`);
31
- });
32
-
33
- test('a rejected frame does not move the clock forward', () => {
34
- // Otherwise a burst of rejected frames would keep pushing the next allowed one further out.
35
- const budget = createFrameBudget();
36
- budget.shouldRender(0);
37
- assert.equal(budget.shouldRender(10), false);
38
- assert.equal(budget.shouldRender(20), false);
39
- assert.equal(budget.shouldRender(30), true);
40
- });
41
-
42
- test('reset makes the next frame the first one again', () => {
43
- const budget = createFrameBudget();
44
- budget.shouldRender(0);
45
- assert.equal(budget.shouldRender(5), false);
46
- budget.reset();
47
- assert.equal(budget.shouldRender(5), true);
48
- });
49
-
50
- test('a missing timestamp never blocks a frame', () => {
51
- // rAF always supplies one, but `renderOnce` can be driven by tooling that does not.
52
- const budget = createFrameBudget();
53
- assert.equal(budget.shouldRender(undefined), true);
54
- assert.equal(budget.shouldRender(Number.NaN), true);
55
- });
56
-
57
- test('a custom rate is honoured', () => {
58
- const budget = createFrameBudget({ fps: 10, toleranceMs: 0 });
59
- assert.equal(budget.shouldRender(0), true);
60
- assert.equal(budget.shouldRender(99), false);
61
- assert.equal(budget.shouldRender(100), true);
62
- });