@vib3code/sdk 2.0.3-canary.590fbae → 2.0.3-canary.69d53b3
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/DOCS/AGENT_HARNESS_ARCHITECTURE.md +2 -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 +2 -0
- package/DOCS/CONTROL_REFERENCE.md +2 -0
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +2 -0
- package/DOCS/ENV_SETUP.md +2 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +2 -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 +2 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +2 -0
- package/DOCS/LICENSING_TIERS.md +2 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +2 -0
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +3 -1
- package/DOCS/OBS_SETUP_GUIDE.md +2 -0
- package/DOCS/OPTIMIZATION_PLAN_MATH.md +119 -0
- package/DOCS/PRODUCT_STRATEGY.md +2 -0
- package/DOCS/PROJECT_SETUP.md +2 -0
- package/DOCS/README.md +5 -3
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +2 -0
- package/DOCS/RENDERER_LIFECYCLE.md +2 -0
- package/DOCS/REPO_MANIFEST.md +2 -0
- package/DOCS/ROADMAP.md +2 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +2 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +2 -0
- package/DOCS/STATUS.md +2 -0
- package/DOCS/SYSTEM_INVENTORY.md +2 -0
- package/DOCS/TELEMETRY_EXPORTS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +2 -0
- package/DOCS/WEBGPU_STATUS.md +2 -0
- package/DOCS/XR_BENCHMARKS.md +2 -0
- package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -34
- package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -80
- package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -42
- package/DOCS/archive/SESSION_014_PLAN.md +1 -195
- package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -56
- package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -72
- package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -741
- package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -38
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-01-31.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +2 -0
- package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +2 -0
- package/DOCS/dev-tracks/README.md +2 -0
- package/package.json +1 -1
- 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/geometry/generators/Crystal.js +2 -2
- package/src/math/Mat4x4.js +238 -92
- package/src/math/Rotor4D.js +69 -46
- package/src/math/Vec4.js +200 -103
- package/src/scene/Node4D.js +74 -24
|
@@ -1,38 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
This document records the current WebGPU backend state, what is implemented, and what is required
|
|
4
|
-
to validate it in local development or CI.
|
|
5
|
-
|
|
6
|
-
## Current status
|
|
7
|
-
- **Backend scaffold:** `WebGPUBackend` initializes adapter/device, configures the canvas, manages
|
|
8
|
-
resize, and supports a clear-pass `renderFrame()` path.
|
|
9
|
-
- **Async context creation:** `createRenderContextAsync()` can instantiate WebGPU contexts using
|
|
10
|
-
`{ backend: 'webgpu' }`.
|
|
11
|
-
- **Resource tracking:** depth textures are registered in the shared `RenderResourceRegistry`.
|
|
12
|
-
|
|
13
|
-
## What is still needed
|
|
14
|
-
1. **Pipeline parity:** implement basic shader pipelines (vertex/fragment) and buffer binding that
|
|
15
|
-
match the WebGL backend command flow.
|
|
16
|
-
2. **Command buffer bridge:** map existing render commands to WebGPU render passes.
|
|
17
|
-
3. **Feature gating:** add host-app controls to toggle WebGPU via feature flags.
|
|
18
|
-
4. **Diagnostics:** add per-frame stats and resource delta reporting for WebGPU.
|
|
19
|
-
|
|
20
|
-
## Testing requirements
|
|
21
|
-
### Local smoke test
|
|
22
|
-
Prerequisites:
|
|
23
|
-
- A browser with WebGPU enabled (Chrome/Edge with `chrome://flags/#enable-unsafe-webgpu`, or a
|
|
24
|
-
Chromium build with WebGPU support).
|
|
25
|
-
- A device with WebGPU-capable GPU drivers.
|
|
26
|
-
|
|
27
|
-
Suggested smoke flow:
|
|
28
|
-
1. Create a canvas and call `createRenderContextAsync(canvas, { backend: 'webgpu' })`.
|
|
29
|
-
2. Call `backend.renderFrame({ clearColor: [0.1, 0.1, 0.2, 1] })` and confirm the canvas clears.
|
|
30
|
-
3. Resize the canvas and ensure the clear pass still succeeds.
|
|
31
|
-
|
|
32
|
-
### CI validation
|
|
33
|
-
- WebGPU cannot be reliably validated in headless CI without GPU support.
|
|
34
|
-
- CI should instead run WebGL tests and lint/static checks; keep a manual WebGPU smoke checklist.
|
|
35
|
-
|
|
36
|
-
## File locations
|
|
37
|
-
- `src/render/backends/WebGPUBackend.js`
|
|
38
|
-
- `src/render/index.js` (`createRenderContextAsync`)
|
|
1
|
+
Last reviewed: 2026-02-17
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vib3code/sdk",
|
|
3
|
-
"version": "2.0.3-canary.
|
|
3
|
+
"version": "2.0.3-canary.69d53b3",
|
|
4
4
|
"description": "VIB3+ 4D Visualization SDK - Unified engine with 6D rotation, MCP agentic integration, and cross-platform support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/core/VIB3Engine.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
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveDirector - Autonomous Creative Agent
|
|
3
|
+
*
|
|
4
|
+
* An AI agent that analyzes user input ("Audience Reaction") and adjusts the
|
|
5
|
+
* VIB3Universe in real-time to maintain engagement, flow, and narrative tension.
|
|
6
|
+
*
|
|
7
|
+
* @experimental
|
|
8
|
+
*/
|
|
9
|
+
export class LiveDirector {
|
|
10
|
+
constructor(universe) {
|
|
11
|
+
this.universe = universe;
|
|
12
|
+
this.active = false;
|
|
13
|
+
|
|
14
|
+
// Audience State
|
|
15
|
+
this.audience = {
|
|
16
|
+
energy: 0.5, // 0.0 (Bored) -> 1.0 (Excited)
|
|
17
|
+
attention: 0.8, // 0.0 (Distracted) -> 1.0 (Focused)
|
|
18
|
+
sentiment: 0.0, // -1.0 (Negative) -> 1.0 (Positive)
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Directing State
|
|
22
|
+
this.pacing = 'build'; // 'intro', 'build', 'climax', 'resolve'
|
|
23
|
+
this.lastActionTime = 0;
|
|
24
|
+
this.decisionInterval = 2000; // ms
|
|
25
|
+
|
|
26
|
+
// Bind methods
|
|
27
|
+
this.update = this.update.bind(this);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Start the director loop.
|
|
32
|
+
*/
|
|
33
|
+
start() {
|
|
34
|
+
if (this.active) return;
|
|
35
|
+
this.active = true;
|
|
36
|
+
this.lastActionTime = performance.now();
|
|
37
|
+
requestAnimationFrame(this.update);
|
|
38
|
+
console.log('LiveDirector: Started directing.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Stop the director loop.
|
|
43
|
+
*/
|
|
44
|
+
stop() {
|
|
45
|
+
this.active = false;
|
|
46
|
+
console.log('LiveDirector: Stopped directing.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Feed audience input signal.
|
|
51
|
+
* @param {string} type - 'audio', 'video', 'input'
|
|
52
|
+
* @param {object} data - Analysis data
|
|
53
|
+
*/
|
|
54
|
+
feedInput(type, data) {
|
|
55
|
+
if (type === 'audio') {
|
|
56
|
+
// Loud audio = high energy
|
|
57
|
+
this.audience.energy = Math.min(1.0, this.audience.energy + data.volume * 0.1);
|
|
58
|
+
} else if (type === 'input') {
|
|
59
|
+
// Interaction = high attention
|
|
60
|
+
this.audience.attention = Math.min(1.0, this.audience.attention + 0.05);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Decay logic runs in update loop
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Main decision loop.
|
|
68
|
+
* @param {number} timestamp
|
|
69
|
+
*/
|
|
70
|
+
update(timestamp) {
|
|
71
|
+
if (!this.active) return;
|
|
72
|
+
|
|
73
|
+
const dt = (timestamp - this.lastActionTime);
|
|
74
|
+
|
|
75
|
+
// Decay audience metrics over time
|
|
76
|
+
this.audience.energy = Math.max(0, this.audience.energy - 0.001);
|
|
77
|
+
this.audience.attention = Math.max(0, this.audience.attention - 0.0005);
|
|
78
|
+
|
|
79
|
+
// Make a directing decision periodically
|
|
80
|
+
if (dt > this.decisionInterval) {
|
|
81
|
+
this.makeDecision();
|
|
82
|
+
this.lastActionTime = timestamp;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
requestAnimationFrame(this.update);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The "Brain" of the Director.
|
|
90
|
+
* Decides what to change based on current state.
|
|
91
|
+
*/
|
|
92
|
+
makeDecision() {
|
|
93
|
+
const { energy, attention } = this.audience;
|
|
94
|
+
|
|
95
|
+
console.log(`LiveDirector: Audience state - Energy: ${energy.toFixed(2)}, Attention: ${attention.toFixed(2)}`);
|
|
96
|
+
|
|
97
|
+
// Strategy: Maintain a "Sine Wave" of tension
|
|
98
|
+
// If energy is too low, spike it. If too high, calm it down.
|
|
99
|
+
|
|
100
|
+
if (attention < 0.3) {
|
|
101
|
+
// Lost attention -> TRIGGER EVENT
|
|
102
|
+
this.triggerEvent('focus_snap');
|
|
103
|
+
} else if (energy < 0.2) {
|
|
104
|
+
// Boring -> INCREASE INTENSITY
|
|
105
|
+
this.adjustGlobalParams({ speed: 1.5, chaos: 0.4 });
|
|
106
|
+
} else if (energy > 0.8) {
|
|
107
|
+
// Too frantic -> CALM DOWN
|
|
108
|
+
this.adjustGlobalParams({ speed: 0.5, chaos: 0.1 });
|
|
109
|
+
} else {
|
|
110
|
+
// Just right -> DO NOTHING or subtle shift
|
|
111
|
+
// Maybe drift hue slightly?
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Execute a parameter adjustment across all actors.
|
|
117
|
+
* @param {object} params
|
|
118
|
+
*/
|
|
119
|
+
adjustGlobalParams(params) {
|
|
120
|
+
console.log('LiveDirector: Adjusting global parameters', params);
|
|
121
|
+
this.universe.actors.forEach(actor => {
|
|
122
|
+
if (actor.animator) {
|
|
123
|
+
actor.animator.transition(params, 2000, 'easeInOut');
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Trigger a specific scripted event.
|
|
130
|
+
* @param {string} eventName
|
|
131
|
+
*/
|
|
132
|
+
triggerEvent(eventName) {
|
|
133
|
+
console.log(`LiveDirector: Triggering event '${eventName}'`);
|
|
134
|
+
// Example: Flash screen, spawn particle burst, etc.
|
|
135
|
+
if (eventName === 'focus_snap') {
|
|
136
|
+
// Quick snap zoom / flash
|
|
137
|
+
this.adjustGlobalParams({ intensity: 1.0, speed: 0.0 });
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
this.adjustGlobalParams({ intensity: 0.5, speed: 1.0 });
|
|
140
|
+
}, 200);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlayerController4D - Navigation in Hyperspace
|
|
3
|
+
*
|
|
4
|
+
* Maps 2D/3D inputs (WASD, Mouse) into 4D motion vectors (X, Y, Z, W).
|
|
5
|
+
* Handles movement, strafing, and "portal rotation" (XW/YW planes).
|
|
6
|
+
*
|
|
7
|
+
* @experimental
|
|
8
|
+
*/
|
|
9
|
+
export class PlayerController4D {
|
|
10
|
+
/**
|
|
11
|
+
* @param {HTMLElement} domElement - Element to listen for events on
|
|
12
|
+
* @param {object} engine - VIB3Engine instance to drive
|
|
13
|
+
*/
|
|
14
|
+
constructor(domElement, engine) {
|
|
15
|
+
this.domElement = domElement;
|
|
16
|
+
this.engine = engine;
|
|
17
|
+
|
|
18
|
+
// Input State
|
|
19
|
+
this.keys = {
|
|
20
|
+
w: false, a: false, s: false, d: false,
|
|
21
|
+
q: false, e: false, space: false, shift: false
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
this.mouse = {
|
|
25
|
+
dx: 0, dy: 0,
|
|
26
|
+
down: false
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Player Physics State
|
|
30
|
+
this.velocity = { x: 0, y: 0, z: 0, w: 0 };
|
|
31
|
+
this.rotation = { x: 0, y: 0 }; // Looking direction (Pitch/Yaw)
|
|
32
|
+
this.portalRot = 0; // XW plane rotation
|
|
33
|
+
|
|
34
|
+
// Config
|
|
35
|
+
this.speed = 5.0;
|
|
36
|
+
this.sensitivity = 0.002;
|
|
37
|
+
this.damping = 0.9;
|
|
38
|
+
|
|
39
|
+
// Bind events
|
|
40
|
+
this.onKeyDown = this.onKeyDown.bind(this);
|
|
41
|
+
this.onKeyUp = this.onKeyUp.bind(this);
|
|
42
|
+
this.onMouseMove = this.onMouseMove.bind(this);
|
|
43
|
+
this.onMouseDown = this.onMouseDown.bind(this);
|
|
44
|
+
this.onMouseUp = this.onMouseUp.bind(this);
|
|
45
|
+
|
|
46
|
+
this.setupListeners();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setupListeners() {
|
|
50
|
+
window.addEventListener('keydown', this.onKeyDown);
|
|
51
|
+
window.addEventListener('keyup', this.onKeyUp);
|
|
52
|
+
this.domElement.addEventListener('mousemove', this.onMouseMove);
|
|
53
|
+
this.domElement.addEventListener('mousedown', this.onMouseDown);
|
|
54
|
+
this.domElement.addEventListener('mouseup', this.onMouseUp);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onKeyDown(e) {
|
|
58
|
+
const k = e.key.toLowerCase();
|
|
59
|
+
if (this.keys.hasOwnProperty(k)) this.keys[k] = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onKeyUp(e) {
|
|
63
|
+
const k = e.key.toLowerCase();
|
|
64
|
+
if (this.keys.hasOwnProperty(k)) this.keys[k] = false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
onMouseDown() { this.mouse.down = true; }
|
|
68
|
+
onMouseUp() { this.mouse.down = false; }
|
|
69
|
+
|
|
70
|
+
onMouseMove(e) {
|
|
71
|
+
if (this.mouse.down) {
|
|
72
|
+
this.mouse.dx += e.movementX;
|
|
73
|
+
this.mouse.dy += e.movementY;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Update loop called by GameLoop.
|
|
79
|
+
* @param {number} dt
|
|
80
|
+
*/
|
|
81
|
+
update(dt) {
|
|
82
|
+
// 1. Process Rotation (Mouse)
|
|
83
|
+
this.rotation.y -= this.mouse.dx * this.sensitivity; // Yaw
|
|
84
|
+
this.rotation.x -= this.mouse.dy * this.sensitivity; // Pitch
|
|
85
|
+
|
|
86
|
+
// Clamp pitch to avoid flipping
|
|
87
|
+
this.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.rotation.x));
|
|
88
|
+
|
|
89
|
+
// Reset mouse delta (consumed)
|
|
90
|
+
this.mouse.dx = 0;
|
|
91
|
+
this.mouse.dy = 0;
|
|
92
|
+
|
|
93
|
+
// 2. Process Movement (WASD)
|
|
94
|
+
// Forward vector derived from Yaw
|
|
95
|
+
const fwdX = Math.sin(this.rotation.y);
|
|
96
|
+
const fwdZ = Math.cos(this.rotation.y);
|
|
97
|
+
const rightX = Math.cos(this.rotation.y); // Perpendicular
|
|
98
|
+
const rightZ = -Math.sin(this.rotation.y);
|
|
99
|
+
|
|
100
|
+
const moveSpeed = this.speed * dt;
|
|
101
|
+
|
|
102
|
+
if (this.keys.w) {
|
|
103
|
+
this.velocity.x += fwdX * moveSpeed;
|
|
104
|
+
this.velocity.z -= fwdZ * moveSpeed; // WebGL Z is negative forward
|
|
105
|
+
}
|
|
106
|
+
if (this.keys.s) {
|
|
107
|
+
this.velocity.x -= fwdX * moveSpeed;
|
|
108
|
+
this.velocity.z += fwdZ * moveSpeed;
|
|
109
|
+
}
|
|
110
|
+
if (this.keys.a) {
|
|
111
|
+
this.velocity.x -= rightX * moveSpeed;
|
|
112
|
+
this.velocity.z += rightZ * moveSpeed;
|
|
113
|
+
}
|
|
114
|
+
if (this.keys.d) {
|
|
115
|
+
this.velocity.x += rightX * moveSpeed;
|
|
116
|
+
this.velocity.z -= rightZ * moveSpeed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Vertical Movement (Space/Shift)
|
|
120
|
+
if (this.keys.space) this.velocity.y += moveSpeed;
|
|
121
|
+
if (this.keys.shift) this.velocity.y -= moveSpeed;
|
|
122
|
+
|
|
123
|
+
// 4D Portal Rotation (Q/E)
|
|
124
|
+
if (this.keys.q) this.portalRot -= moveSpeed;
|
|
125
|
+
if (this.keys.e) this.portalRot += moveSpeed;
|
|
126
|
+
|
|
127
|
+
// Apply Damping (Friction)
|
|
128
|
+
this.velocity.x *= this.damping;
|
|
129
|
+
this.velocity.y *= this.damping;
|
|
130
|
+
this.velocity.z *= this.damping;
|
|
131
|
+
|
|
132
|
+
// 3. Apply to VIB3Engine Parameters
|
|
133
|
+
// Map 4D position to shader uniforms (e.g., u_noiseOffset or camera pos)
|
|
134
|
+
// Here we map to rotation parameters as a proxy for camera movement
|
|
135
|
+
|
|
136
|
+
// Visual feedback: Velocity tilts the view
|
|
137
|
+
this.engine.setParameter('rot4dXY', this.rotation.y + this.velocity.x * 0.1);
|
|
138
|
+
this.engine.setParameter('rot4dYZ', this.rotation.x + this.velocity.y * 0.1);
|
|
139
|
+
|
|
140
|
+
// Portal rotation affects XW plane
|
|
141
|
+
this.engine.setParameter('rot4dXW', this.portalRot);
|
|
142
|
+
|
|
143
|
+
// "Moving forward" increases grid density/scale to simulate zooming through
|
|
144
|
+
// In a real implementation, we'd update a u_cameraPosition uniform
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
destroy() {
|
|
148
|
+
window.removeEventListener('keydown', this.onKeyDown);
|
|
149
|
+
window.removeEventListener('keyup', this.onKeyUp);
|
|
150
|
+
this.domElement.removeEventListener('mousemove', this.onMouseMove);
|
|
151
|
+
this.domElement.removeEventListener('mousedown', this.onMouseDown);
|
|
152
|
+
this.domElement.removeEventListener('mouseup', this.onMouseUp);
|
|
153
|
+
}
|
|
154
|
+
}
|