@vib3code/sdk 2.0.1 → 2.0.3-canary.0a63e71
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.md +36 -0
- package/DOCS/AGENT_HARNESS_ARCHITECTURE.md +245 -0
- package/DOCS/ANDROID_DEPLOYMENT.md +59 -0
- package/DOCS/ARCHITECTURE.md +1 -0
- package/DOCS/CI_TESTING.md +2 -0
- package/DOCS/CLI_ONBOARDING.md +3 -1
- package/DOCS/CONTROL_REFERENCE.md +2 -0
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +119 -0
- package/DOCS/ENV_SETUP.md +2 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +775 -0
- package/DOCS/EXPANSION_DESIGN.md +979 -0
- package/DOCS/EXPANSION_DESIGN_ULTRA.md +389 -0
- package/DOCS/EXPORT_FORMATS.md +2 -0
- package/DOCS/GPU_DISPOSAL_GUIDE.md +2 -0
- package/DOCS/HANDOFF_LANDING_PAGE.md +156 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +495 -0
- package/DOCS/LICENSING_TIERS.md +2 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +4 -2
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +939 -0
- package/DOCS/OBS_SETUP_GUIDE.md +2 -0
- package/DOCS/OPTIMIZATION_PLAN_MATH.md +119 -0
- package/DOCS/PRODUCT_STRATEGY.md +65 -0
- package/DOCS/PROJECT_SETUP.md +2 -0
- package/DOCS/README.md +105 -0
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +99 -0
- package/DOCS/RENDERER_LIFECYCLE.md +2 -0
- package/DOCS/REPO_MANIFEST.md +2 -0
- package/DOCS/ROADMAP.md +113 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +271 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +102 -0
- package/DOCS/STATUS.md +26 -0
- package/DOCS/SYSTEM_INVENTORY.md +37 -32
- package/DOCS/TELEMETRY_EXPORTS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +87 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +135 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +97 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +88 -0
- package/DOCS/WEBGPU_STATUS.md +121 -38
- package/DOCS/XR_BENCHMARKS.md +2 -0
- package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -0
- package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -0
- package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -0
- package/DOCS/archive/SESSION_014_PLAN.md +1 -0
- package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -0
- package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -0
- package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -0
- package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -0
- package/DOCS/{DEV_TRACK_SESSION_2026-01-31.md → dev-tracks/DEV_TRACK_SESSION_2026-01-31.md} +3 -1
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +233 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +129 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +144 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +110 -0
- package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +310 -0
- package/DOCS/dev-tracks/README.md +12 -0
- package/README.md +26 -13
- package/cpp/CMakeLists.txt +236 -0
- package/cpp/bindings/embind.cpp +269 -0
- package/cpp/build.sh +129 -0
- package/cpp/geometry/Crystal.cpp +103 -0
- package/cpp/geometry/Fractal.cpp +136 -0
- package/cpp/geometry/GeometryGenerator.cpp +262 -0
- package/cpp/geometry/KleinBottle.cpp +71 -0
- package/cpp/geometry/Sphere.cpp +134 -0
- package/cpp/geometry/Tesseract.cpp +94 -0
- package/cpp/geometry/Tetrahedron.cpp +83 -0
- package/cpp/geometry/Torus.cpp +65 -0
- package/cpp/geometry/WarpFunctions.cpp +238 -0
- package/cpp/geometry/Wave.cpp +85 -0
- package/cpp/include/vib3_ffi.h +238 -0
- package/cpp/math/Mat4x4.cpp +409 -0
- package/cpp/math/Mat4x4.hpp +209 -0
- package/cpp/math/Projection.cpp +142 -0
- package/cpp/math/Projection.hpp +148 -0
- package/cpp/math/Rotor4D.cpp +322 -0
- package/cpp/math/Rotor4D.hpp +204 -0
- package/cpp/math/Vec4.cpp +303 -0
- package/cpp/math/Vec4.hpp +225 -0
- package/cpp/src/vib3_ffi.cpp +607 -0
- package/cpp/tests/Geometry_test.cpp +213 -0
- package/cpp/tests/Mat4x4_test.cpp +494 -0
- package/cpp/tests/Projection_test.cpp +298 -0
- package/cpp/tests/Rotor4D_test.cpp +423 -0
- package/cpp/tests/Vec4_test.cpp +489 -0
- package/docs/webgpu-live.html +1 -1
- package/package.json +41 -30
- package/src/agent/index.js +1 -3
- package/src/agent/mcp/MCPServer.js +1220 -144
- package/src/agent/mcp/index.js +1 -1
- package/src/agent/mcp/stdio-server.js +264 -0
- package/src/agent/mcp/tools.js +498 -31
- package/src/cli/index.js +431 -47
- package/src/core/CanvasManager.js +97 -204
- package/src/core/ErrorReporter.js +1 -1
- package/src/core/Parameters.js +1 -1
- package/src/core/VIB3Engine.js +93 -4
- package/src/core/VitalitySystem.js +53 -0
- package/src/core/index.js +18 -0
- package/src/core/renderers/FacetedRendererAdapter.js +10 -9
- package/src/core/renderers/HolographicRendererAdapter.js +13 -9
- package/src/core/renderers/QuantumRendererAdapter.js +11 -7
- package/src/creative/AestheticMapper.js +628 -0
- package/src/creative/ChoreographyPlayer.js +481 -0
- package/src/creative/index.js +11 -0
- package/src/experimental/GameLoop.js +72 -0
- package/src/experimental/LatticePhysics.js +100 -0
- package/src/experimental/LiveDirector.js +143 -0
- package/src/experimental/PlayerController4D.js +154 -0
- package/src/experimental/VIB3Actor.js +138 -0
- package/src/experimental/VIB3Compositor.js +117 -0
- package/src/experimental/VIB3Link.js +122 -0
- package/src/experimental/VIB3Orchestrator.js +146 -0
- package/src/experimental/VIB3Universe.js +109 -0
- package/src/experimental/demos/CrystalLabyrinth.js +202 -0
- package/src/export/TradingCardManager.js +3 -4
- package/src/export/index.js +11 -1
- package/src/faceted/FacetedSystem.js +260 -394
- package/src/games/glyph-war/GlyphWarVisualizer.js +641 -0
- package/src/geometry/generators/Crystal.js +2 -2
- package/src/geometry/warp/HypersphereCore.js +53 -24
- package/src/holograms/HolographicVisualizer.js +84 -98
- package/src/holograms/RealHolographicSystem.js +194 -43
- package/src/math/Mat4x4.js +308 -105
- package/src/math/Rotor4D.js +124 -40
- package/src/math/Vec4.js +200 -103
- package/src/math/index.js +7 -7
- package/src/polychora/PolychoraSystem.js +77 -0
- package/src/quantum/QuantumEngine.js +103 -66
- package/src/quantum/QuantumVisualizer.js +31 -22
- package/src/reactivity/index.js +3 -5
- package/src/render/LayerPresetManager.js +372 -0
- package/src/render/LayerReactivityBridge.js +344 -0
- package/src/render/LayerRelationshipGraph.js +610 -0
- package/src/render/MultiCanvasBridge.js +148 -25
- package/src/render/ShaderLoader.js +38 -0
- package/src/render/ShaderProgram.js +4 -4
- package/src/render/UnifiedRenderBridge.js +4 -1
- package/src/render/backends/WebGPUBackend.js +8 -4
- package/src/render/index.js +27 -2
- package/src/scene/Node4D.js +74 -24
- package/src/scene/index.js +4 -4
- package/src/shaders/common/geometry24.glsl +65 -0
- package/src/shaders/common/geometry24.wgsl +54 -0
- package/src/shaders/common/rotation4d.glsl +4 -4
- package/src/shaders/common/rotation4d.wgsl +2 -2
- package/src/shaders/common/uniforms.wgsl +15 -8
- package/src/shaders/faceted/faceted.frag.glsl +220 -80
- package/src/shaders/faceted/faceted.frag.wgsl +144 -90
- package/src/shaders/holographic/holographic.frag.glsl +28 -9
- package/src/shaders/holographic/holographic.frag.wgsl +112 -41
- package/src/shaders/quantum/quantum.frag.glsl +1 -0
- package/src/shaders/quantum/quantum.frag.wgsl +6 -4
- package/src/testing/ParallelTestFramework.js +2 -2
- package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +2 -2
- package/src/viewer/GalleryUI.js +17 -0
- package/src/viewer/ViewerPortal.js +2 -2
- package/src/viewer/index.js +1 -1
- package/tools/headless-renderer.js +258 -0
- package/tools/shader-sync-verify.js +14 -8
- package/tools/site-analysis/all-reports.json +32 -0
- package/tools/site-analysis/combined-analysis.md +50 -0
- package/tools/site-analyzer.mjs +779 -0
- package/tools/visual-catalog/capture.js +276 -0
- package/tools/visual-catalog/composite.js +138 -0
- package/types/adaptive-sdk.d.ts +204 -5
- package/types/agent/cli.d.ts +78 -0
- package/types/agent/index.d.ts +18 -0
- package/types/agent/mcp.d.ts +87 -0
- package/types/agent/telemetry.d.ts +190 -0
- package/types/core/VIB3Engine.d.ts +26 -0
- package/types/core/index.d.ts +261 -0
- package/types/creative/AestheticMapper.d.ts +72 -0
- package/types/creative/ChoreographyPlayer.d.ts +96 -0
- package/types/creative/index.d.ts +17 -0
- package/types/export/index.d.ts +243 -0
- package/types/geometry/index.d.ts +164 -0
- package/types/math/index.d.ts +214 -0
- package/types/render/LayerPresetManager.d.ts +78 -0
- package/types/render/LayerReactivityBridge.d.ts +85 -0
- package/types/render/LayerRelationshipGraph.d.ts +174 -0
- package/types/render/index.d.ts +3 -0
- package/types/scene/index.d.ts +204 -0
- package/types/systems/index.d.ts +244 -0
- package/types/variations/index.d.ts +62 -0
- package/types/viewer/index.d.ts +225 -0
- package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +0 -34
- package/DOCS/DEV_TRACK_ANALYSIS.md +0 -77
- package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +0 -42
- package/DOCS/SESSION_014_PLAN.md +0 -195
- package/DOCS/SESSION_LOG_2026-01-07.md +0 -56
- package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +0 -72
- package/DOCS/SYSTEM_AUDIT_2026-01-30.md +0 -738
- /package/src/viewer/{ReactivityManager.js → ViewerInputHandler.js} +0 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChoreographyPlayer.js - VIB3+ Multi-Scene Choreography Playback Runtime
|
|
3
|
+
*
|
|
4
|
+
* Plays back choreography specifications created by the `create_choreography`
|
|
5
|
+
* MCP tool. Orchestrates system switching, geometry changes, per-scene
|
|
6
|
+
* ParameterTimeline playback, color presets, post-processing, and scene
|
|
7
|
+
* transitions across the full choreography duration.
|
|
8
|
+
*
|
|
9
|
+
* @module creative/ChoreographyPlayer
|
|
10
|
+
* @version 1.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ParameterTimeline } from './ParameterTimeline.js';
|
|
14
|
+
import { TransitionAnimator } from './TransitionAnimator.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} ChoreographyScene
|
|
18
|
+
* @property {number} index - Scene index
|
|
19
|
+
* @property {number} time_start - Start time (ms)
|
|
20
|
+
* @property {number} time_end - End time (ms)
|
|
21
|
+
* @property {string} system - Visualization system name
|
|
22
|
+
* @property {number} geometry - Geometry index (0-23)
|
|
23
|
+
* @property {Object} transition_in - Entry transition config
|
|
24
|
+
* @property {Object} tracks - Per-scene parameter timeline tracks
|
|
25
|
+
* @property {string|null} color_preset - Color preset name
|
|
26
|
+
* @property {string[]} post_processing - Active effect names
|
|
27
|
+
* @property {Object|null} audio - Audio reactivity config
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} ChoreographySpec
|
|
32
|
+
* @property {string} id - Choreography ID
|
|
33
|
+
* @property {string} name - Choreography name
|
|
34
|
+
* @property {number} duration_ms - Total duration
|
|
35
|
+
* @property {number|null} bpm - BPM for beat sync
|
|
36
|
+
* @property {number} scene_count - Number of scenes
|
|
37
|
+
* @property {ChoreographyScene[]} scenes - Scene array
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Plays multi-scene VIB3+ choreographies.
|
|
42
|
+
*
|
|
43
|
+
* Manages the lifecycle of scenes: at each scene boundary, it switches
|
|
44
|
+
* the visualization system, sets geometry, loads per-scene timelines,
|
|
45
|
+
* applies color presets and post-processing, and handles transitions
|
|
46
|
+
* between scenes.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* const player = new ChoreographyPlayer(engine);
|
|
50
|
+
* player.load(choreographySpec);
|
|
51
|
+
* player.play();
|
|
52
|
+
*
|
|
53
|
+
* // Seek to 50%
|
|
54
|
+
* player.seekToPercent(0.5);
|
|
55
|
+
*
|
|
56
|
+
* // Pause / resume
|
|
57
|
+
* player.pause();
|
|
58
|
+
* player.play();
|
|
59
|
+
*/
|
|
60
|
+
export class ChoreographyPlayer {
|
|
61
|
+
/**
|
|
62
|
+
* @param {Object} engine - VIB3Engine instance (or any object with
|
|
63
|
+
* setParameter, getParameter, switchSystem, getCurrentSystem methods)
|
|
64
|
+
* @param {Object} [options]
|
|
65
|
+
* @param {Function} [options.onSceneChange] - Callback(sceneIndex, scene) on scene transitions
|
|
66
|
+
* @param {Function} [options.onComplete] - Callback when choreography finishes (once mode)
|
|
67
|
+
* @param {Function} [options.onTick] - Callback(currentTime, totalDuration) each frame
|
|
68
|
+
*/
|
|
69
|
+
constructor(engine, options = {}) {
|
|
70
|
+
if (!engine) {
|
|
71
|
+
throw new Error('ChoreographyPlayer requires a VIB3Engine instance');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @type {Object} */
|
|
75
|
+
this.engine = engine;
|
|
76
|
+
|
|
77
|
+
/** @type {ChoreographySpec|null} */
|
|
78
|
+
this.spec = null;
|
|
79
|
+
|
|
80
|
+
/** @type {boolean} */
|
|
81
|
+
this.playing = false;
|
|
82
|
+
|
|
83
|
+
/** @type {number} Current playback position in ms */
|
|
84
|
+
this.currentTime = 0;
|
|
85
|
+
|
|
86
|
+
/** @type {number} Playback speed multiplier */
|
|
87
|
+
this.playbackSpeed = 1.0;
|
|
88
|
+
|
|
89
|
+
/** @type {'once'|'loop'} */
|
|
90
|
+
this.loopMode = 'once';
|
|
91
|
+
|
|
92
|
+
/** @type {number} Index of currently active scene (-1 if none) */
|
|
93
|
+
this.activeSceneIndex = -1;
|
|
94
|
+
|
|
95
|
+
/** @type {ParameterTimeline|null} Current scene's timeline */
|
|
96
|
+
this._sceneTimeline = null;
|
|
97
|
+
|
|
98
|
+
/** @type {TransitionAnimator} Shared transition animator */
|
|
99
|
+
this._transitionAnimator = new TransitionAnimator(
|
|
100
|
+
(name, value) => this.engine.setParameter(name, value),
|
|
101
|
+
(name) => this.engine.getParameter?.(name) ?? 0
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
/** @type {number|null} requestAnimationFrame ID */
|
|
105
|
+
this._frameId = null;
|
|
106
|
+
|
|
107
|
+
/** @type {number} Last frame timestamp */
|
|
108
|
+
this._lastFrameTime = 0;
|
|
109
|
+
|
|
110
|
+
// Callbacks
|
|
111
|
+
this._onSceneChange = options.onSceneChange || null;
|
|
112
|
+
this._onComplete = options.onComplete || null;
|
|
113
|
+
this._onTick = options.onTick || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
117
|
+
// Public API
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Load a choreography specification.
|
|
122
|
+
*
|
|
123
|
+
* @param {ChoreographySpec} spec - Choreography data (from create_choreography MCP tool
|
|
124
|
+
* or parsed from choreography_json)
|
|
125
|
+
* @returns {boolean} true if loaded successfully
|
|
126
|
+
*/
|
|
127
|
+
load(spec) {
|
|
128
|
+
if (!spec || !Array.isArray(spec.scenes) || spec.scenes.length === 0) {
|
|
129
|
+
console.warn('ChoreographyPlayer: Invalid spec — needs scenes array');
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.stop();
|
|
134
|
+
|
|
135
|
+
this.spec = {
|
|
136
|
+
id: spec.id || `choreo_${Date.now()}`,
|
|
137
|
+
name: spec.name || 'Untitled',
|
|
138
|
+
duration_ms: spec.duration_ms || this._inferDuration(spec.scenes),
|
|
139
|
+
bpm: spec.bpm || null,
|
|
140
|
+
scene_count: spec.scenes.length,
|
|
141
|
+
scenes: spec.scenes.map((s, i) => ({
|
|
142
|
+
index: s.index ?? i,
|
|
143
|
+
time_start: s.time_start ?? 0,
|
|
144
|
+
time_end: s.time_end ?? spec.duration_ms,
|
|
145
|
+
system: s.system || 'quantum',
|
|
146
|
+
geometry: s.geometry ?? 0,
|
|
147
|
+
transition_in: s.transition_in || { type: 'cut', duration: 0 },
|
|
148
|
+
tracks: s.tracks || {},
|
|
149
|
+
color_preset: s.color_preset || null,
|
|
150
|
+
post_processing: s.post_processing || [],
|
|
151
|
+
audio: s.audio || null
|
|
152
|
+
}))
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Sort scenes by start time
|
|
156
|
+
this.spec.scenes.sort((a, b) => a.time_start - b.time_start);
|
|
157
|
+
|
|
158
|
+
this.currentTime = 0;
|
|
159
|
+
this.activeSceneIndex = -1;
|
|
160
|
+
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Start or resume playback.
|
|
166
|
+
*/
|
|
167
|
+
play() {
|
|
168
|
+
if (!this.spec) {
|
|
169
|
+
console.warn('ChoreographyPlayer: No choreography loaded');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (this.playing) return;
|
|
174
|
+
|
|
175
|
+
this.playing = true;
|
|
176
|
+
this._lastFrameTime = performance.now();
|
|
177
|
+
|
|
178
|
+
// Enter the current scene if not already in one
|
|
179
|
+
this._evaluateScene(this.currentTime);
|
|
180
|
+
|
|
181
|
+
this._tick();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Pause playback (maintains position).
|
|
186
|
+
*/
|
|
187
|
+
pause() {
|
|
188
|
+
this.playing = false;
|
|
189
|
+
if (this._frameId !== null) {
|
|
190
|
+
cancelAnimationFrame(this._frameId);
|
|
191
|
+
this._frameId = null;
|
|
192
|
+
}
|
|
193
|
+
if (this._sceneTimeline) {
|
|
194
|
+
this._sceneTimeline.pause();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Stop playback and reset to beginning.
|
|
200
|
+
*/
|
|
201
|
+
stop() {
|
|
202
|
+
this.pause();
|
|
203
|
+
this.currentTime = 0;
|
|
204
|
+
this.activeSceneIndex = -1;
|
|
205
|
+
if (this._sceneTimeline) {
|
|
206
|
+
this._sceneTimeline.stop();
|
|
207
|
+
this._sceneTimeline = null;
|
|
208
|
+
}
|
|
209
|
+
this._transitionAnimator.cancelAll();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Seek to an absolute time position.
|
|
214
|
+
* @param {number} timeMs - Time in milliseconds
|
|
215
|
+
*/
|
|
216
|
+
seek(timeMs) {
|
|
217
|
+
if (!this.spec) return;
|
|
218
|
+
this.currentTime = Math.max(0, Math.min(timeMs, this.spec.duration_ms));
|
|
219
|
+
this._evaluateScene(this.currentTime);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Seek to a percentage of the total duration.
|
|
224
|
+
* @param {number} percent - 0.0 to 1.0
|
|
225
|
+
*/
|
|
226
|
+
seekToPercent(percent) {
|
|
227
|
+
if (!this.spec) return;
|
|
228
|
+
this.seek(Math.max(0, Math.min(1, percent)) * this.spec.duration_ms);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get current playback state.
|
|
233
|
+
* @returns {Object} State summary
|
|
234
|
+
*/
|
|
235
|
+
getState() {
|
|
236
|
+
const scene = this.spec?.scenes[this.activeSceneIndex] ?? null;
|
|
237
|
+
return {
|
|
238
|
+
playing: this.playing,
|
|
239
|
+
currentTime: this.currentTime,
|
|
240
|
+
duration: this.spec?.duration_ms ?? 0,
|
|
241
|
+
progress: this.spec ? this.currentTime / this.spec.duration_ms : 0,
|
|
242
|
+
activeScene: scene ? {
|
|
243
|
+
index: scene.index,
|
|
244
|
+
system: scene.system,
|
|
245
|
+
geometry: scene.geometry,
|
|
246
|
+
time_start: scene.time_start,
|
|
247
|
+
time_end: scene.time_end,
|
|
248
|
+
scene_progress: scene.time_end > scene.time_start
|
|
249
|
+
? (this.currentTime - scene.time_start) / (scene.time_end - scene.time_start)
|
|
250
|
+
: 0
|
|
251
|
+
} : null,
|
|
252
|
+
sceneCount: this.spec?.scene_count ?? 0,
|
|
253
|
+
playbackSpeed: this.playbackSpeed
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Clean up resources.
|
|
259
|
+
*/
|
|
260
|
+
destroy() {
|
|
261
|
+
this.stop();
|
|
262
|
+
this._transitionAnimator.destroy?.() || this._transitionAnimator.cancelAll();
|
|
263
|
+
this.spec = null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
267
|
+
// Internal
|
|
268
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Main animation loop.
|
|
272
|
+
*/
|
|
273
|
+
_tick() {
|
|
274
|
+
if (!this.playing) return;
|
|
275
|
+
|
|
276
|
+
const now = performance.now();
|
|
277
|
+
const delta = (now - this._lastFrameTime) * this.playbackSpeed;
|
|
278
|
+
this._lastFrameTime = now;
|
|
279
|
+
|
|
280
|
+
this.currentTime += delta;
|
|
281
|
+
|
|
282
|
+
// Handle end-of-choreography
|
|
283
|
+
if (this.currentTime >= this.spec.duration_ms) {
|
|
284
|
+
if (this.loopMode === 'loop') {
|
|
285
|
+
this.currentTime = this.currentTime % this.spec.duration_ms;
|
|
286
|
+
this.activeSceneIndex = -1; // Force re-evaluation
|
|
287
|
+
} else {
|
|
288
|
+
this.currentTime = this.spec.duration_ms;
|
|
289
|
+
this.playing = false;
|
|
290
|
+
if (this._onComplete) this._onComplete();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Evaluate which scene we should be in
|
|
296
|
+
this._evaluateScene(this.currentTime);
|
|
297
|
+
|
|
298
|
+
// Update scene timeline position (relative to scene start)
|
|
299
|
+
if (this._sceneTimeline && this._sceneTimeline.playing) {
|
|
300
|
+
const scene = this.spec.scenes[this.activeSceneIndex];
|
|
301
|
+
const sceneLocalTime = this.currentTime - scene.time_start;
|
|
302
|
+
this._sceneTimeline.seek(sceneLocalTime);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Tick callback
|
|
306
|
+
if (this._onTick) {
|
|
307
|
+
this._onTick(this.currentTime, this.spec.duration_ms);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this._frameId = requestAnimationFrame(() => this._tick());
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Determine which scene should be active at the given time,
|
|
315
|
+
* and transition if needed.
|
|
316
|
+
*/
|
|
317
|
+
_evaluateScene(timeMs) {
|
|
318
|
+
if (!this.spec) return;
|
|
319
|
+
|
|
320
|
+
// Find the scene that contains this time
|
|
321
|
+
let targetIndex = -1;
|
|
322
|
+
for (let i = 0; i < this.spec.scenes.length; i++) {
|
|
323
|
+
const scene = this.spec.scenes[i];
|
|
324
|
+
if (timeMs >= scene.time_start && timeMs < scene.time_end) {
|
|
325
|
+
targetIndex = i;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// If no scene found and we're at the very end, use the last scene
|
|
331
|
+
if (targetIndex === -1 && timeMs >= this.spec.duration_ms && this.spec.scenes.length > 0) {
|
|
332
|
+
targetIndex = this.spec.scenes.length - 1;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Scene change needed?
|
|
336
|
+
if (targetIndex !== this.activeSceneIndex && targetIndex >= 0) {
|
|
337
|
+
this._enterScene(targetIndex);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Enter a new scene — switch system, geometry, load timeline, apply presets.
|
|
343
|
+
*/
|
|
344
|
+
async _enterScene(sceneIndex) {
|
|
345
|
+
const scene = this.spec.scenes[sceneIndex];
|
|
346
|
+
const previousIndex = this.activeSceneIndex;
|
|
347
|
+
this.activeSceneIndex = sceneIndex;
|
|
348
|
+
|
|
349
|
+
// Stop previous scene timeline
|
|
350
|
+
if (this._sceneTimeline) {
|
|
351
|
+
this._sceneTimeline.stop();
|
|
352
|
+
this._sceneTimeline = null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// 1. Handle scene transition (crossfade, etc.)
|
|
356
|
+
const transition = scene.transition_in;
|
|
357
|
+
if (transition && transition.type !== 'cut' && transition.duration > 0 && previousIndex >= 0) {
|
|
358
|
+
// For non-cut transitions, we do a smooth crossfade via intensity
|
|
359
|
+
this._transitionAnimator.transition(
|
|
360
|
+
{ intensity: 0 },
|
|
361
|
+
transition.duration / 2,
|
|
362
|
+
'easeIn',
|
|
363
|
+
() => {
|
|
364
|
+
// Midpoint: switch system/geometry while faded out
|
|
365
|
+
this._applyCoreSceneState(scene);
|
|
366
|
+
this._transitionAnimator.transition(
|
|
367
|
+
{ intensity: scene.tracks?.intensity?.[0]?.value ?? 0.5 },
|
|
368
|
+
transition.duration / 2,
|
|
369
|
+
'easeOut'
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
} else {
|
|
374
|
+
// Cut: immediately apply
|
|
375
|
+
await this._applyCoreSceneState(scene);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 2. Build and start per-scene ParameterTimeline
|
|
379
|
+
if (scene.tracks && Object.keys(scene.tracks).length > 0) {
|
|
380
|
+
const sceneDuration = scene.time_end - scene.time_start;
|
|
381
|
+
this._sceneTimeline = new ParameterTimeline(
|
|
382
|
+
(name, value) => this.engine.setParameter(name, value)
|
|
383
|
+
);
|
|
384
|
+
this._sceneTimeline.setDuration(sceneDuration);
|
|
385
|
+
this._sceneTimeline.setLoopMode('once');
|
|
386
|
+
|
|
387
|
+
if (this.spec.bpm) {
|
|
388
|
+
this._sceneTimeline.bpm = this.spec.bpm;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (const [param, keyframes] of Object.entries(scene.tracks)) {
|
|
392
|
+
this._sceneTimeline.addTrack(param);
|
|
393
|
+
if (Array.isArray(keyframes)) {
|
|
394
|
+
for (const kf of keyframes) {
|
|
395
|
+
this._sceneTimeline.addKeyframe(
|
|
396
|
+
param,
|
|
397
|
+
kf.time ?? 0,
|
|
398
|
+
kf.value ?? 0,
|
|
399
|
+
kf.easing || 'easeInOut'
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Seek to the correct position within the scene
|
|
406
|
+
const sceneLocalTime = this.currentTime - scene.time_start;
|
|
407
|
+
this._sceneTimeline.seek(Math.max(0, sceneLocalTime));
|
|
408
|
+
if (this.playing) {
|
|
409
|
+
this._sceneTimeline.play();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 3. Notify callback
|
|
414
|
+
if (this._onSceneChange) {
|
|
415
|
+
this._onSceneChange(sceneIndex, scene);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Apply the core state of a scene: system, geometry, color preset.
|
|
421
|
+
*/
|
|
422
|
+
async _applyCoreSceneState(scene) {
|
|
423
|
+
// Switch system if different
|
|
424
|
+
const currentSystem = this.engine.getCurrentSystem?.() || this.engine.currentSystemName;
|
|
425
|
+
if (scene.system && scene.system !== currentSystem) {
|
|
426
|
+
if (this.engine.switchSystem) {
|
|
427
|
+
await this.engine.switchSystem(scene.system);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Set geometry
|
|
432
|
+
if (scene.geometry !== undefined) {
|
|
433
|
+
this.engine.setParameter('geometry', scene.geometry);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Apply color preset (simplified — maps preset name to hue/sat/intensity)
|
|
437
|
+
if (scene.color_preset) {
|
|
438
|
+
const colors = ChoreographyPlayer.COLOR_PRESET_MAP[scene.color_preset];
|
|
439
|
+
if (colors) {
|
|
440
|
+
this.engine.setParameter('hue', colors.hue);
|
|
441
|
+
this.engine.setParameter('saturation', colors.saturation);
|
|
442
|
+
this.engine.setParameter('intensity', colors.intensity);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Infer total duration from scene end times.
|
|
449
|
+
*/
|
|
450
|
+
_inferDuration(scenes) {
|
|
451
|
+
return Math.max(...scenes.map(s => s.time_end || 0), 10000);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Color preset lookup (matches MCPServer.applyColorPreset).
|
|
456
|
+
*/
|
|
457
|
+
static COLOR_PRESET_MAP = {
|
|
458
|
+
Ocean: { hue: 200, saturation: 0.8, intensity: 0.6 },
|
|
459
|
+
Lava: { hue: 15, saturation: 0.9, intensity: 0.8 },
|
|
460
|
+
Neon: { hue: 300, saturation: 1.0, intensity: 0.9 },
|
|
461
|
+
Monochrome: { hue: 0, saturation: 0.0, intensity: 0.6 },
|
|
462
|
+
Sunset: { hue: 30, saturation: 0.85, intensity: 0.7 },
|
|
463
|
+
Aurora: { hue: 140, saturation: 0.7, intensity: 0.6 },
|
|
464
|
+
Cyberpunk: { hue: 280, saturation: 0.9, intensity: 0.8 },
|
|
465
|
+
Forest: { hue: 120, saturation: 0.6, intensity: 0.5 },
|
|
466
|
+
Desert: { hue: 40, saturation: 0.5, intensity: 0.7 },
|
|
467
|
+
Galaxy: { hue: 260, saturation: 0.8, intensity: 0.4 },
|
|
468
|
+
Ice: { hue: 190, saturation: 0.5, intensity: 0.8 },
|
|
469
|
+
Fire: { hue: 10, saturation: 1.0, intensity: 0.9 },
|
|
470
|
+
Toxic: { hue: 100, saturation: 0.9, intensity: 0.7 },
|
|
471
|
+
Royal: { hue: 270, saturation: 0.7, intensity: 0.5 },
|
|
472
|
+
Pastel: { hue: 330, saturation: 0.3, intensity: 0.8 },
|
|
473
|
+
Retro: { hue: 50, saturation: 0.7, intensity: 0.6 },
|
|
474
|
+
Midnight: { hue: 240, saturation: 0.6, intensity: 0.3 },
|
|
475
|
+
Tropical: { hue: 160, saturation: 0.8, intensity: 0.7 },
|
|
476
|
+
Ethereal: { hue: 220, saturation: 0.4, intensity: 0.7 },
|
|
477
|
+
Volcanic: { hue: 5, saturation: 0.95, intensity: 0.6 },
|
|
478
|
+
Holographic: { hue: 180, saturation: 0.6, intensity: 0.8 },
|
|
479
|
+
Vaporwave: { hue: 310, saturation: 0.7, intensity: 0.7 }
|
|
480
|
+
};
|
|
481
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIB3+ Creative Tooling Module
|
|
3
|
+
* @module creative
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { ColorPresetsSystem } from './ColorPresetsSystem.js';
|
|
7
|
+
export { TransitionAnimator } from './TransitionAnimator.js';
|
|
8
|
+
export { PostProcessingPipeline } from './PostProcessingPipeline.js';
|
|
9
|
+
export { ParameterTimeline } from './ParameterTimeline.js';
|
|
10
|
+
export { ChoreographyPlayer } from './ChoreographyPlayer.js';
|
|
11
|
+
export { AestheticMapper } from './AestheticMapper.js';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GameLoop - Fixed-Timestep Physics Loop
|
|
3
|
+
*
|
|
4
|
+
* Provides a robust game loop that decouples physics updates (fixed step)
|
|
5
|
+
* from rendering (variable step). This is crucial for consistent physics
|
|
6
|
+
* and smooth rendering across different device capabilities.
|
|
7
|
+
*
|
|
8
|
+
* @experimental
|
|
9
|
+
*/
|
|
10
|
+
export class GameLoop {
|
|
11
|
+
/**
|
|
12
|
+
* @param {function(number)} updateFn - Physics update (fixed dt)
|
|
13
|
+
* @param {function(number)} renderFn - Render update (interpolated alpha)
|
|
14
|
+
* @param {number} step - Physics step size in seconds (default 1/60)
|
|
15
|
+
*/
|
|
16
|
+
constructor(updateFn, renderFn, step = 1 / 60) {
|
|
17
|
+
this.updateFn = updateFn;
|
|
18
|
+
this.renderFn = renderFn;
|
|
19
|
+
this.step = step;
|
|
20
|
+
this.dt = 0;
|
|
21
|
+
this.last = 0;
|
|
22
|
+
this.now = 0;
|
|
23
|
+
this.accumulator = 0;
|
|
24
|
+
this.running = false;
|
|
25
|
+
this.rafId = null;
|
|
26
|
+
|
|
27
|
+
// Bind loop
|
|
28
|
+
this.frame = this.frame.bind(this);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
start() {
|
|
32
|
+
if (this.running) return;
|
|
33
|
+
this.running = true;
|
|
34
|
+
this.last = performance.now();
|
|
35
|
+
this.accumulator = 0;
|
|
36
|
+
this.rafId = requestAnimationFrame(this.frame);
|
|
37
|
+
console.log('GameLoop: Started.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
stop() {
|
|
41
|
+
this.running = false;
|
|
42
|
+
if (this.rafId) {
|
|
43
|
+
cancelAnimationFrame(this.rafId);
|
|
44
|
+
this.rafId = null;
|
|
45
|
+
}
|
|
46
|
+
console.log('GameLoop: Stopped.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
frame(timestamp) {
|
|
50
|
+
if (!this.running) return;
|
|
51
|
+
|
|
52
|
+
this.now = timestamp;
|
|
53
|
+
// Cap dt to avoid "spiral of death" on lag spikes (max 1s)
|
|
54
|
+
this.dt = Math.min(1, (this.now - this.last) / 1000);
|
|
55
|
+
this.last = this.now;
|
|
56
|
+
|
|
57
|
+
this.accumulator += this.dt;
|
|
58
|
+
|
|
59
|
+
// Consume accumulator in fixed steps
|
|
60
|
+
while (this.accumulator >= this.step) {
|
|
61
|
+
this.updateFn(this.step);
|
|
62
|
+
this.accumulator -= this.step;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Render with interpolation factor alpha
|
|
66
|
+
// alpha = accumulator / step
|
|
67
|
+
// Allows renderer to interpolate between previous and current physics state
|
|
68
|
+
this.renderFn(this.accumulator / this.step);
|
|
69
|
+
|
|
70
|
+
this.rafId = requestAnimationFrame(this.frame);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LatticePhysics - Function-Based Collision Detection
|
|
3
|
+
*
|
|
4
|
+
* Provides a physics engine for "Lattice Worlds" where geometry is defined
|
|
5
|
+
* by mathematical density functions (SDFs, fractals).
|
|
6
|
+
*
|
|
7
|
+
* @experimental
|
|
8
|
+
*/
|
|
9
|
+
export class LatticePhysics {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.gravity = 9.8; // m/s²
|
|
12
|
+
this.friction = 0.95; // Velocity decay per step
|
|
13
|
+
this.densityThreshold = 0.8; // Collision threshold (0-1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Update physics for all entities in the universe.
|
|
18
|
+
* @param {Map<string, object>} entities
|
|
19
|
+
* @param {number} dt
|
|
20
|
+
*/
|
|
21
|
+
update(entities, dt) {
|
|
22
|
+
entities.forEach(entity => {
|
|
23
|
+
if (entity.physics && entity.active) {
|
|
24
|
+
this.updateEntity(entity, dt);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Update a single entity's physics state.
|
|
31
|
+
* @param {object} entity
|
|
32
|
+
* @param {number} dt
|
|
33
|
+
*/
|
|
34
|
+
updateEntity(entity, dt) {
|
|
35
|
+
const { pos, vel, acc } = entity.physics;
|
|
36
|
+
|
|
37
|
+
// Apply forces (Gravity)
|
|
38
|
+
// In this abstract world, gravity pulls "down" in Y
|
|
39
|
+
acc.y -= this.gravity * dt;
|
|
40
|
+
|
|
41
|
+
// Integrate Velocity (Euler)
|
|
42
|
+
vel.x += acc.x * dt;
|
|
43
|
+
vel.y += acc.y * dt;
|
|
44
|
+
vel.z += acc.z * dt;
|
|
45
|
+
|
|
46
|
+
// Reset acceleration (forces are transient)
|
|
47
|
+
acc.x = 0; acc.y = 0; acc.z = 0;
|
|
48
|
+
|
|
49
|
+
// Collision Check (Projected Position)
|
|
50
|
+
const nextX = pos.x + vel.x * dt;
|
|
51
|
+
const nextY = pos.y + vel.y * dt;
|
|
52
|
+
const nextZ = pos.z + vel.z * dt;
|
|
53
|
+
|
|
54
|
+
// Sample density at next position
|
|
55
|
+
const density = this.getDensityAt(nextX, nextY, nextZ);
|
|
56
|
+
|
|
57
|
+
if (density > this.densityThreshold) {
|
|
58
|
+
// Collision!
|
|
59
|
+
// Simple response: Stop velocity component and push out
|
|
60
|
+
// A real engine would calculate surface normal from gradient
|
|
61
|
+
|
|
62
|
+
// Simplified: Just stop movement and bounce slightly
|
|
63
|
+
vel.x *= -0.5;
|
|
64
|
+
vel.y *= -0.5;
|
|
65
|
+
vel.z *= -0.5;
|
|
66
|
+
|
|
67
|
+
// Don't update position into solid
|
|
68
|
+
} else {
|
|
69
|
+
// Move freely
|
|
70
|
+
pos.x = nextX;
|
|
71
|
+
pos.y = nextY;
|
|
72
|
+
pos.z = nextZ;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Apply Friction (Air/Ether drag)
|
|
76
|
+
vel.x *= this.friction;
|
|
77
|
+
vel.y *= this.friction;
|
|
78
|
+
vel.z *= this.friction;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Sample the "world density" at a given point.
|
|
83
|
+
* This mimics the shader's generation logic (e.g., fractal noise).
|
|
84
|
+
* @param {number} x
|
|
85
|
+
* @param {number} y
|
|
86
|
+
* @param {number} z
|
|
87
|
+
* @returns {number} Density 0.0 to 1.0
|
|
88
|
+
*/
|
|
89
|
+
getDensityAt(x, y, z) {
|
|
90
|
+
// Mock function: simple floor plane at y = -2
|
|
91
|
+
if (y < -2) return 1.0;
|
|
92
|
+
|
|
93
|
+
// Mock function: occasional "floating islands" based on sine waves
|
|
94
|
+
// simulating the VIB3 lattice structure
|
|
95
|
+
const noise = (Math.sin(x * 0.5) + Math.cos(z * 0.5)) * 0.5 + 0.5;
|
|
96
|
+
if (y > 0 && y < 1 && noise > 0.8) return 1.0;
|
|
97
|
+
|
|
98
|
+
return 0.0;
|
|
99
|
+
}
|
|
100
|
+
}
|