Spaces:
Sleeping
Sleeping
feat(galaxy): larger UMAP world and slower Galaxy flight profile
Browse filesScale 96 + spacing×240 and Galaxy-only move/look profile (32 / 0.0014);
leaving Galaxy restores ANALYSIS/NAV defaults.
Co-authored-by: Cursor <cursoragent@cursor.com>
- src/engine/Navigation.js +9 -4
- src/engine/galaxyFlightProfile.js +46 -0
- src/main.js +8 -1
- src/visualizer/Instancer.js +2 -5
- src/visualizer/galaxyLayout.js +28 -2
- tests/Navigation.test.js +26 -0
- tests/galaxyFlightProfile.test.js +51 -0
- tests/galaxyInstancer.test.js +22 -0
- tests/galaxyLayout.test.js +21 -0
src/engine/Navigation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
import * as THREE from 'three';
|
| 2 |
import { resolveCameraPose, VIEW_CAMERA_FALLBACKS } from './cameraViewDefaults.js';
|
|
|
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Inertial Flight & Camera Navigation Controller (WASDQE + Mouse Drag Look + Shift Turbo).
|
|
@@ -21,8 +22,9 @@ export class Navigation {
|
|
| 21 |
};
|
| 22 |
|
| 23 |
this.velocity = new THREE.Vector3();
|
| 24 |
-
this.moveSpeed =
|
| 25 |
-
this.turboMultiplier =
|
|
|
|
| 26 |
this.damping = 0.85;
|
| 27 |
this._cameraTweenRaf = null;
|
| 28 |
|
|
@@ -63,9 +65,12 @@ export class Navigation {
|
|
| 63 |
* @param {number} deltaY
|
| 64 |
*/
|
| 65 |
applyLookDelta(deltaX, deltaY) {
|
|
|
|
|
|
|
|
|
|
| 66 |
this.euler.setFromQuaternion(this.camera.quaternion);
|
| 67 |
-
this.euler.y -= deltaX *
|
| 68 |
-
this.euler.x -= deltaY *
|
| 69 |
this.euler.x = Math.max(-Math.PI / 2 + 0.05, Math.min(Math.PI / 2 - 0.05, this.euler.x));
|
| 70 |
this.camera.quaternion.setFromEuler(this.euler);
|
| 71 |
}
|
|
|
|
| 1 |
import * as THREE from 'three';
|
| 2 |
import { resolveCameraPose, VIEW_CAMERA_FALLBACKS } from './cameraViewDefaults.js';
|
| 3 |
+
import { DEFAULT_FLIGHT_PROFILE } from './galaxyFlightProfile.js';
|
| 4 |
|
| 5 |
/**
|
| 6 |
* Inertial Flight & Camera Navigation Controller (WASDQE + Mouse Drag Look + Shift Turbo).
|
|
|
|
| 22 |
};
|
| 23 |
|
| 24 |
this.velocity = new THREE.Vector3();
|
| 25 |
+
this.moveSpeed = DEFAULT_FLIGHT_PROFILE.moveSpeed;
|
| 26 |
+
this.turboMultiplier = DEFAULT_FLIGHT_PROFILE.turboMultiplier;
|
| 27 |
+
this.lookSensitivity = DEFAULT_FLIGHT_PROFILE.lookSensitivity;
|
| 28 |
this.damping = 0.85;
|
| 29 |
this._cameraTweenRaf = null;
|
| 30 |
|
|
|
|
| 65 |
* @param {number} deltaY
|
| 66 |
*/
|
| 67 |
applyLookDelta(deltaX, deltaY) {
|
| 68 |
+
const sens = Number.isFinite(this.lookSensitivity)
|
| 69 |
+
? this.lookSensitivity
|
| 70 |
+
: DEFAULT_FLIGHT_PROFILE.lookSensitivity;
|
| 71 |
this.euler.setFromQuaternion(this.camera.quaternion);
|
| 72 |
+
this.euler.y -= deltaX * sens;
|
| 73 |
+
this.euler.x -= deltaY * sens;
|
| 74 |
this.euler.x = Math.max(-Math.PI / 2 + 0.05, Math.min(Math.PI / 2 - 0.05, this.euler.x));
|
| 75 |
this.camera.quaternion.setFromEuler(this.euler);
|
| 76 |
}
|
src/engine/galaxyFlightProfile.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Flight feel profiles for Navigation (WASD/QE + mouse look).
|
| 3 |
+
* Galaxy VIEW uses a slower profile; ANALYSIS/NAVIGATION keep defaults.
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
/** @typedef {{ moveSpeed: number, turboMultiplier: number, lookSensitivity: number }} FlightProfile */
|
| 7 |
+
|
| 8 |
+
/** Current ANALYSIS / NAVIGATION feel (pre–Galaxy-feel baseline). */
|
| 9 |
+
export const DEFAULT_FLIGHT_PROFILE = Object.freeze({
|
| 10 |
+
moveSpeed: 75.0,
|
| 11 |
+
turboMultiplier: 2.0,
|
| 12 |
+
lookSensitivity: 0.003,
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Galaxy-only: slower cruise + softer look so a larger UMAP world feels elegant.
|
| 17 |
+
* Calibrated with GALAXY_DEFAULT_SCALE ≈ 96 (RMS≈1 → ~several seconds across IT-core).
|
| 18 |
+
*/
|
| 19 |
+
export const GALAXY_FLIGHT_PROFILE = Object.freeze({
|
| 20 |
+
moveSpeed: 32.0,
|
| 21 |
+
turboMultiplier: 1.75,
|
| 22 |
+
lookSensitivity: 0.0014,
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
/**
|
| 26 |
+
* @param {{ moveSpeed?: number, turboMultiplier?: number, lookSensitivity?: number }|null|undefined} nav
|
| 27 |
+
* @param {FlightProfile|null|undefined} profile
|
| 28 |
+
* @returns {typeof nav}
|
| 29 |
+
*/
|
| 30 |
+
export function applyFlightProfile(nav, profile) {
|
| 31 |
+
if (!nav || !profile) return nav;
|
| 32 |
+
nav.moveSpeed = profile.moveSpeed;
|
| 33 |
+
nav.turboMultiplier = profile.turboMultiplier;
|
| 34 |
+
nav.lookSensitivity = profile.lookSensitivity;
|
| 35 |
+
return nav;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/** @param {object|null|undefined} nav */
|
| 39 |
+
export function applyGalaxyFlightProfile(nav) {
|
| 40 |
+
return applyFlightProfile(nav, GALAXY_FLIGHT_PROFILE);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/** @param {object|null|undefined} nav */
|
| 44 |
+
export function restoreDefaultFlightProfile(nav) {
|
| 45 |
+
return applyFlightProfile(nav, DEFAULT_FLIGHT_PROFILE);
|
| 46 |
+
}
|
src/main.js
CHANGED
|
@@ -65,6 +65,10 @@ import {
|
|
| 65 |
} from './ui/galaxyChrome.js';
|
| 66 |
import { runGalaxyPipeline, compareTextsFingerprint } from './ui/galaxyPipeline.js';
|
| 67 |
import { galaxyCameraTarget } from './visualizer/galaxyLayout.js';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
import {
|
| 69 |
loadArithmeticSettings,
|
| 70 |
saveArithmeticSettings,
|
|
@@ -390,6 +394,7 @@ class VHectorLabApp {
|
|
| 390 |
this._preGalaxyTriad = entered.restore;
|
| 391 |
this.viewMode = entered.viewMode;
|
| 392 |
this.galaxyMethod = entered.method;
|
|
|
|
| 393 |
this.navbar.setModeRenderLocked(true);
|
| 394 |
this.navbar.setGalaxyMethod(entered.method);
|
| 395 |
this.navbar.setViewMode(entered.viewMode);
|
|
@@ -417,6 +422,7 @@ class VHectorLabApp {
|
|
| 417 |
this._galaxyFramePending = false;
|
| 418 |
this.comparePanel?.clearGalaxyProgress?.();
|
| 419 |
this.viewMode = left.viewMode;
|
|
|
|
| 420 |
this.navbar.setModeRenderLocked(false);
|
| 421 |
this.navbar.setViewMode(left.viewMode);
|
| 422 |
|
|
@@ -636,7 +642,8 @@ class VHectorLabApp {
|
|
| 636 |
// Prefer fit-box so sparse cores stay readable; look-at is IT centroid when present.
|
| 637 |
const box = target.box.clone();
|
| 638 |
// Inflate slightly so focus isn't clipped
|
| 639 |
-
|
|
|
|
| 640 |
void this.navigation.animateToFitBox(box, {
|
| 641 |
viewMode: 'NAVIGATION',
|
| 642 |
durationMs: 550,
|
|
|
|
| 65 |
} from './ui/galaxyChrome.js';
|
| 66 |
import { runGalaxyPipeline, compareTextsFingerprint } from './ui/galaxyPipeline.js';
|
| 67 |
import { galaxyCameraTarget } from './visualizer/galaxyLayout.js';
|
| 68 |
+
import {
|
| 69 |
+
applyGalaxyFlightProfile,
|
| 70 |
+
restoreDefaultFlightProfile,
|
| 71 |
+
} from './engine/galaxyFlightProfile.js';
|
| 72 |
import {
|
| 73 |
loadArithmeticSettings,
|
| 74 |
saveArithmeticSettings,
|
|
|
|
| 394 |
this._preGalaxyTriad = entered.restore;
|
| 395 |
this.viewMode = entered.viewMode;
|
| 396 |
this.galaxyMethod = entered.method;
|
| 397 |
+
applyGalaxyFlightProfile(this.navigation);
|
| 398 |
this.navbar.setModeRenderLocked(true);
|
| 399 |
this.navbar.setGalaxyMethod(entered.method);
|
| 400 |
this.navbar.setViewMode(entered.viewMode);
|
|
|
|
| 422 |
this._galaxyFramePending = false;
|
| 423 |
this.comparePanel?.clearGalaxyProgress?.();
|
| 424 |
this.viewMode = left.viewMode;
|
| 425 |
+
restoreDefaultFlightProfile(this.navigation);
|
| 426 |
this.navbar.setModeRenderLocked(false);
|
| 427 |
this.navbar.setViewMode(left.viewMode);
|
| 428 |
|
|
|
|
| 642 |
// Prefer fit-box so sparse cores stay readable; look-at is IT centroid when present.
|
| 643 |
const box = target.box.clone();
|
| 644 |
// Inflate slightly so focus isn't clipped
|
| 645 |
+
// Inflate padding scales with cloud size so fit framing stays comfortable
|
| 646 |
+
box.expandByScalar(24);
|
| 647 |
void this.navigation.animateToFitBox(box, {
|
| 648 |
viewMode: 'NAVIGATION',
|
| 649 |
durationMs: 550,
|
src/visualizer/Instancer.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
| 13 |
computeDimRelationMetrics,
|
| 14 |
hasGroupsForDimContrast,
|
| 15 |
} from './groupDimContrast.js';
|
| 16 |
-
import {
|
| 17 |
|
| 18 |
/**
|
| 19 |
* Instancer Manager for rendering vector points, ribbons, and highlights in the Three.js scene.
|
|
@@ -431,10 +431,7 @@ export class Instancer {
|
|
| 431 |
const thicknessFactor = (spatialConfig && spatialConfig.threadThickness !== undefined)
|
| 432 |
? spatialConfig.threadThickness
|
| 433 |
: 0.3;
|
| 434 |
-
|
| 435 |
-
const scale = (spatialConfig && spatialConfig.threadSpacing !== undefined)
|
| 436 |
-
? Math.max(8, Number(spatialConfig.threadSpacing) * 40)
|
| 437 |
-
: GALAXY_DEFAULT_SCALE;
|
| 438 |
|
| 439 |
const { pointsData, labels } = layoutGalaxyPoints(
|
| 440 |
compareResponse.items,
|
|
|
|
| 13 |
computeDimRelationMetrics,
|
| 14 |
hasGroupsForDimContrast,
|
| 15 |
} from './groupDimContrast.js';
|
| 16 |
+
import { layoutGalaxyPoints, resolveGalaxyWorldScale } from './galaxyLayout.js';
|
| 17 |
|
| 18 |
/**
|
| 19 |
* Instancer Manager for rendering vector points, ribbons, and highlights in the Three.js scene.
|
|
|
|
| 431 |
const thicknessFactor = (spatialConfig && spatialConfig.threadThickness !== undefined)
|
| 432 |
? spatialConfig.threadThickness
|
| 433 |
: 0.3;
|
| 434 |
+
const scale = resolveGalaxyWorldScale(spatialConfig);
|
|
|
|
|
|
|
|
|
|
| 435 |
|
| 436 |
const { pointsData, labels } = layoutGalaxyPoints(
|
| 437 |
compareResponse.items,
|
src/visualizer/galaxyLayout.js
CHANGED
|
@@ -6,8 +6,34 @@
|
|
| 6 |
import * as THREE from 'three';
|
| 7 |
import { IT_CORE_GROUP_ID } from '../ui/itCoreCorpus.js';
|
| 8 |
|
| 9 |
-
/**
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
/**
|
| 13 |
* @param {Array<{ id?: string, text?: string, groupId?: string, groupLabel?: string, cosine_vs_first?: number }>} items
|
|
|
|
| 6 |
import * as THREE from 'three';
|
| 7 |
import { IT_CORE_GROUP_ID } from '../ui/itCoreCorpus.js';
|
| 8 |
|
| 9 |
+
/**
|
| 10 |
+
* Default world scale for server-normalized (RMS≈1) UMAP coords.
|
| 11 |
+
* Raised (was 48) so Galaxy feels spacious with the slower flight profile.
|
| 12 |
+
*/
|
| 13 |
+
export const GALAXY_DEFAULT_SCALE = 96;
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Maps Spatial `threadSpacing` → galaxy world scale.
|
| 17 |
+
* At global default spacing 0.4 → 96 (matches GALAXY_DEFAULT_SCALE).
|
| 18 |
+
* Was 40 (→ scale 16 at spacing 0.4 — cramped).
|
| 19 |
+
*/
|
| 20 |
+
export const GALAXY_SPACING_TO_SCALE = 240;
|
| 21 |
+
|
| 22 |
+
/** Floor so tiny spacing sliders never collapse the cloud. */
|
| 23 |
+
export const GALAXY_MIN_SCALE = 32;
|
| 24 |
+
|
| 25 |
+
/**
|
| 26 |
+
* Resolve Galaxy world scale from Spatial sliders (or default).
|
| 27 |
+
* @param {{ threadSpacing?: number }|null|undefined} spatialConfig
|
| 28 |
+
* @returns {number}
|
| 29 |
+
*/
|
| 30 |
+
export function resolveGalaxyWorldScale(spatialConfig) {
|
| 31 |
+
if (spatialConfig && spatialConfig.threadSpacing !== undefined) {
|
| 32 |
+
const raw = Number(spatialConfig.threadSpacing) * GALAXY_SPACING_TO_SCALE;
|
| 33 |
+
return Math.max(GALAXY_MIN_SCALE, Number.isFinite(raw) ? raw : GALAXY_DEFAULT_SCALE);
|
| 34 |
+
}
|
| 35 |
+
return GALAXY_DEFAULT_SCALE;
|
| 36 |
+
}
|
| 37 |
|
| 38 |
/**
|
| 39 |
* @param {Array<{ id?: string, text?: string, groupId?: string, groupLabel?: string, cosine_vs_first?: number }>} items
|
tests/Navigation.test.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
| 1 |
import { describe, it, expect } from 'vitest';
|
| 2 |
import * as THREE from 'three';
|
| 3 |
import { Navigation } from '../src/engine/Navigation.js';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
function stubCamera() {
|
| 6 |
return {
|
|
@@ -66,3 +72,23 @@ describe('Navigation default poses', () => {
|
|
| 66 |
expect(nav.camera.position.z).toBeCloseTo(52.2, 5);
|
| 67 |
});
|
| 68 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { describe, it, expect } from 'vitest';
|
| 2 |
import * as THREE from 'three';
|
| 3 |
import { Navigation } from '../src/engine/Navigation.js';
|
| 4 |
+
import {
|
| 5 |
+
DEFAULT_FLIGHT_PROFILE,
|
| 6 |
+
GALAXY_FLIGHT_PROFILE,
|
| 7 |
+
applyGalaxyFlightProfile,
|
| 8 |
+
restoreDefaultFlightProfile,
|
| 9 |
+
} from '../src/engine/galaxyFlightProfile.js';
|
| 10 |
|
| 11 |
function stubCamera() {
|
| 12 |
return {
|
|
|
|
| 72 |
expect(nav.camera.position.z).toBeCloseTo(52.2, 5);
|
| 73 |
});
|
| 74 |
});
|
| 75 |
+
|
| 76 |
+
describe('Navigation flight profile', () => {
|
| 77 |
+
it('starts on default profile and applyLookDelta uses lookSensitivity', () => {
|
| 78 |
+
const camera = stubCamera();
|
| 79 |
+
const nav = new Navigation(camera, { addEventListener() {} });
|
| 80 |
+
expect(nav.moveSpeed).toBe(DEFAULT_FLIGHT_PROFILE.moveSpeed);
|
| 81 |
+
expect(nav.lookSensitivity).toBe(DEFAULT_FLIGHT_PROFILE.lookSensitivity);
|
| 82 |
+
|
| 83 |
+
applyGalaxyFlightProfile(nav);
|
| 84 |
+
expect(nav.moveSpeed).toBe(GALAXY_FLIGHT_PROFILE.moveSpeed);
|
| 85 |
+
|
| 86 |
+
nav.euler.set(0, 0, 0, 'YXZ');
|
| 87 |
+
nav.camera.quaternion.setFromEuler(nav.euler);
|
| 88 |
+
nav.applyLookDelta(100, 0);
|
| 89 |
+
expect(nav.euler.y).toBeCloseTo(-100 * GALAXY_FLIGHT_PROFILE.lookSensitivity, 6);
|
| 90 |
+
|
| 91 |
+
restoreDefaultFlightProfile(nav);
|
| 92 |
+
expect(nav.moveSpeed).toBe(DEFAULT_FLIGHT_PROFILE.moveSpeed);
|
| 93 |
+
});
|
| 94 |
+
});
|
tests/galaxyFlightProfile.test.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, expect, it } from 'vitest';
|
| 2 |
+
import {
|
| 3 |
+
DEFAULT_FLIGHT_PROFILE,
|
| 4 |
+
GALAXY_FLIGHT_PROFILE,
|
| 5 |
+
applyFlightProfile,
|
| 6 |
+
applyGalaxyFlightProfile,
|
| 7 |
+
restoreDefaultFlightProfile,
|
| 8 |
+
} from '../src/engine/galaxyFlightProfile.js';
|
| 9 |
+
|
| 10 |
+
function stubNav(overrides = {}) {
|
| 11 |
+
return {
|
| 12 |
+
moveSpeed: DEFAULT_FLIGHT_PROFILE.moveSpeed,
|
| 13 |
+
turboMultiplier: DEFAULT_FLIGHT_PROFILE.turboMultiplier,
|
| 14 |
+
lookSensitivity: DEFAULT_FLIGHT_PROFILE.lookSensitivity,
|
| 15 |
+
...overrides,
|
| 16 |
+
};
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
describe('galaxyFlightProfile', () => {
|
| 20 |
+
it('Galaxy profile is slower translation and look than default', () => {
|
| 21 |
+
expect(GALAXY_FLIGHT_PROFILE.moveSpeed).toBeLessThan(DEFAULT_FLIGHT_PROFILE.moveSpeed);
|
| 22 |
+
expect(GALAXY_FLIGHT_PROFILE.lookSensitivity).toBeLessThan(
|
| 23 |
+
DEFAULT_FLIGHT_PROFILE.lookSensitivity,
|
| 24 |
+
);
|
| 25 |
+
expect(GALAXY_FLIGHT_PROFILE.turboMultiplier).toBeGreaterThan(1);
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
it('applyGalaxyFlightProfile sets slower speeds on nav', () => {
|
| 29 |
+
const nav = stubNav();
|
| 30 |
+
applyGalaxyFlightProfile(nav);
|
| 31 |
+
expect(nav.moveSpeed).toBe(GALAXY_FLIGHT_PROFILE.moveSpeed);
|
| 32 |
+
expect(nav.turboMultiplier).toBe(GALAXY_FLIGHT_PROFILE.turboMultiplier);
|
| 33 |
+
expect(nav.lookSensitivity).toBe(GALAXY_FLIGHT_PROFILE.lookSensitivity);
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
it('restoreDefaultFlightProfile restores ANALYSIS/NAV feel', () => {
|
| 37 |
+
const nav = stubNav();
|
| 38 |
+
applyGalaxyFlightProfile(nav);
|
| 39 |
+
restoreDefaultFlightProfile(nav);
|
| 40 |
+
expect(nav.moveSpeed).toBe(DEFAULT_FLIGHT_PROFILE.moveSpeed);
|
| 41 |
+
expect(nav.turboMultiplier).toBe(DEFAULT_FLIGHT_PROFILE.turboMultiplier);
|
| 42 |
+
expect(nav.lookSensitivity).toBe(DEFAULT_FLIGHT_PROFILE.lookSensitivity);
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
it('applyFlightProfile is a no-op for missing nav/profile', () => {
|
| 46 |
+
expect(applyFlightProfile(null, GALAXY_FLIGHT_PROFILE)).toBeNull();
|
| 47 |
+
const nav = stubNav({ moveSpeed: 99 });
|
| 48 |
+
expect(applyFlightProfile(nav, null)).toBe(nav);
|
| 49 |
+
expect(nav.moveSpeed).toBe(99);
|
| 50 |
+
});
|
| 51 |
+
});
|
tests/galaxyInstancer.test.js
CHANGED
|
@@ -2,6 +2,10 @@ import { describe, expect, it } from 'vitest';
|
|
| 2 |
import * as THREE from 'three';
|
| 3 |
import { Instancer } from '../src/visualizer/Instancer.js';
|
| 4 |
import { IT_CORE_GROUP_ID } from '../src/ui/itCoreCorpus.js';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
describe('Instancer.renderGalaxyData', () => {
|
| 7 |
it('mounts a single POINTS mesh and no ribbons', () => {
|
|
@@ -44,6 +48,7 @@ describe('Instancer.renderGalaxyData', () => {
|
|
| 44 |
expect(labels).toHaveLength(3);
|
| 45 |
expect(labels[0].text).toBe('server');
|
| 46 |
expect(labels[0].groupId).toBe(IT_CORE_GROUP_ID);
|
|
|
|
| 47 |
|
| 48 |
expect(instancer.activeGroup.children).toHaveLength(1);
|
| 49 |
expect(instancer.activeGroup.children[0].type).toBe('Points');
|
|
@@ -51,6 +56,23 @@ describe('Instancer.renderGalaxyData', () => {
|
|
| 51 |
expect(instancer.compareRuntime).toBeNull();
|
| 52 |
});
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
it('returns empty when positions missing', () => {
|
| 55 |
const scene = new THREE.Scene();
|
| 56 |
const instancer = new Instancer(scene);
|
|
|
|
| 2 |
import * as THREE from 'three';
|
| 3 |
import { Instancer } from '../src/visualizer/Instancer.js';
|
| 4 |
import { IT_CORE_GROUP_ID } from '../src/ui/itCoreCorpus.js';
|
| 5 |
+
import {
|
| 6 |
+
GALAXY_DEFAULT_SCALE,
|
| 7 |
+
GALAXY_SPACING_TO_SCALE,
|
| 8 |
+
} from '../src/visualizer/galaxyLayout.js';
|
| 9 |
|
| 10 |
describe('Instancer.renderGalaxyData', () => {
|
| 11 |
it('mounts a single POINTS mesh and no ribbons', () => {
|
|
|
|
| 48 |
expect(labels).toHaveLength(3);
|
| 49 |
expect(labels[0].text).toBe('server');
|
| 50 |
expect(labels[0].groupId).toBe(IT_CORE_GROUP_ID);
|
| 51 |
+
expect(labels[0].origin3D.x).toBeCloseTo(0.1 * GALAXY_DEFAULT_SCALE);
|
| 52 |
|
| 53 |
expect(instancer.activeGroup.children).toHaveLength(1);
|
| 54 |
expect(instancer.activeGroup.children[0].type).toBe('Points');
|
|
|
|
| 56 |
expect(instancer.compareRuntime).toBeNull();
|
| 57 |
});
|
| 58 |
|
| 59 |
+
it('scales from threadSpacing via resolveGalaxyWorldScale', () => {
|
| 60 |
+
const scene = new THREE.Scene();
|
| 61 |
+
const instancer = new Instancer(scene);
|
| 62 |
+
const compareResponse = {
|
| 63 |
+
items: [
|
| 64 |
+
{ id: 'a', text: 'a', embedding: [0], cosine_vs_first: 1 },
|
| 65 |
+
],
|
| 66 |
+
};
|
| 67 |
+
const positions = [[1, 0, 0]];
|
| 68 |
+
const spacing = 0.5;
|
| 69 |
+
const labels = instancer.renderGalaxyData(compareResponse, positions, {
|
| 70 |
+
threadSpacing: spacing,
|
| 71 |
+
threadThickness: 0.05,
|
| 72 |
+
});
|
| 73 |
+
expect(labels[0].origin3D.x).toBeCloseTo(spacing * GALAXY_SPACING_TO_SCALE);
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
it('returns empty when positions missing', () => {
|
| 77 |
const scene = new THREE.Scene();
|
| 78 |
const instancer = new Instancer(scene);
|
tests/galaxyLayout.test.js
CHANGED
|
@@ -5,7 +5,11 @@ import {
|
|
| 5 |
boundingBoxFromLabels,
|
| 6 |
centroidForGroup,
|
| 7 |
galaxyCameraTarget,
|
|
|
|
|
|
|
|
|
|
| 8 |
layoutGalaxyPoints,
|
|
|
|
| 9 |
} from '../src/visualizer/galaxyLayout.js';
|
| 10 |
|
| 11 |
describe('galaxyLayout', () => {
|
|
@@ -27,6 +31,23 @@ describe('galaxyLayout', () => {
|
|
| 27 |
expect(pointsData[0].meta.token).toBe('server');
|
| 28 |
});
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
it('centroidForGroup averages member origins', () => {
|
| 31 |
const labels = [
|
| 32 |
{ groupId: IT_CORE_GROUP_ID, origin3D: new THREE.Vector3(0, 0, 0) },
|
|
|
|
| 5 |
boundingBoxFromLabels,
|
| 6 |
centroidForGroup,
|
| 7 |
galaxyCameraTarget,
|
| 8 |
+
GALAXY_DEFAULT_SCALE,
|
| 9 |
+
GALAXY_MIN_SCALE,
|
| 10 |
+
GALAXY_SPACING_TO_SCALE,
|
| 11 |
layoutGalaxyPoints,
|
| 12 |
+
resolveGalaxyWorldScale,
|
| 13 |
} from '../src/visualizer/galaxyLayout.js';
|
| 14 |
|
| 15 |
describe('galaxyLayout', () => {
|
|
|
|
| 31 |
expect(pointsData[0].meta.token).toBe('server');
|
| 32 |
});
|
| 33 |
|
| 34 |
+
it('default layout scale matches GALAXY_DEFAULT_SCALE', () => {
|
| 35 |
+
const { scale } = layoutGalaxyPoints(
|
| 36 |
+
[{ id: 'a', text: 'x', cosine_vs_first: 0 }],
|
| 37 |
+
[[1, 0, 0]],
|
| 38 |
+
);
|
| 39 |
+
expect(scale).toBe(GALAXY_DEFAULT_SCALE);
|
| 40 |
+
expect(GALAXY_DEFAULT_SCALE).toBeGreaterThan(48);
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
+
it('resolveGalaxyWorldScale maps spacing and floors tiny values', () => {
|
| 44 |
+
expect(resolveGalaxyWorldScale(null)).toBe(GALAXY_DEFAULT_SCALE);
|
| 45 |
+
expect(resolveGalaxyWorldScale({ threadSpacing: 0.4 })).toBeCloseTo(
|
| 46 |
+
0.4 * GALAXY_SPACING_TO_SCALE,
|
| 47 |
+
);
|
| 48 |
+
expect(resolveGalaxyWorldScale({ threadSpacing: 0.01 })).toBe(GALAXY_MIN_SCALE);
|
| 49 |
+
});
|
| 50 |
+
|
| 51 |
it('centroidForGroup averages member origins', () => {
|
| 52 |
const labels = [
|
| 53 |
{ groupId: IT_CORE_GROUP_ID, origin3D: new THREE.Vector3(0, 0, 0) },
|