Files
wehub-resource-sync 9740bc64c9
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier1) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (full-adr060) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (tdm-3node) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / Swarm Test (ADR-062) (push) Has been skipped
npm packages / tools/ruview-mcp (node 22) (push) Failing after 1s
nvsim-server → ghcr.io / build-and-publish (push) Failing after 1s
ruview-swarm CI guard / tests (full+train) (push) Failing after 2s
Bench Regression Guard / bench compile-verify (--no-run) (push) Failing after 0s
Bench Regression Guard / bench fast-run (informational, non-gating) (push) Has been skipped
Firmware CI / Verify version.txt matches release tag (push) Has been skipped
Dashboard a11y + cross-browser / a11y (push) Failing after 0s
nvsim Dashboard → GitHub Pages / build-and-deploy (push) Failing after 2s
Firmware CI / Build firmware (esp32s3 / 4mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / Build Espressif QEMU (push) Failing after 1s
Firmware QEMU Tests (ADR-061) / Fuzz Testing (ADR-061 Layer 6) (push) Failing after 1s
Continuous Deployment / Pre-deployment Checks (push) Has been skipped
Firmware CI / Build firmware (esp32c6 / c6-4mb) (push) Failing after 15s
Firmware CI / Build firmware (esp32s3 / 8mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-max) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-min) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (default) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier0) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / NVS Matrix Generation (push) Failing after 1s
Security Scanning / Security Policy Compliance (push) Failing after 0s
Security Scanning / Dependency Vulnerability Scan (push) Failing after 0s
Security Scanning / Static Application Security Testing (push) Failing after 1s
Security Scanning / Infrastructure Security Scan (push) Failing after 1s
Security Scanning / Secret Scanning (push) Failing after 1s
npm packages / harness/ruview (node 22) (push) Failing after 17s
Security Scanning / License Compliance Scan (push) Failing after 1s
Security Scanning / Container Security Scan (push) Failing after 4s
three.js demos → GitHub Pages / build-and-deploy (push) Failing after 1s
Verify Pipeline Determinism / Verify Pipeline Determinism (3.11) (push) Failing after 1s
Fix-Marker Regression Guard / Verify fix markers (push) Failing after 1s
ADR-115 MQTT integration tests / mqtt-integration (push) Failing after 1s
npm packages / harness/ruview (node 20) (push) Failing after 1s
npm packages / tools/ruview-mcp (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 22) (push) Failing after 1s
BFLD MQTT Integration / cargo test --features mqtt (live mosquitto) (push) Failing after 29s
ruview-swarm CI guard / build train_marl bin (push) Failing after 2s
ruview-swarm CI guard / clippy (-D warnings, --no-deps) (push) Failing after 3s
ruview-swarm CI guard / tests (ruflo) (push) Failing after 1s
ruview-swarm CI guard / tests (train) (push) Failing after 2s
ruview-swarm CI guard / tests (default) (push) Failing after 2s
Point Cloud Viewer → GitHub Pages / build-and-deploy (push) Failing after 8s
ruview-swarm CI guard / ITAR / publish guard (push) Failing after 0s
wifi-densepose sensing-server → Docker Hub + ghcr.io / build · push · smoke-test (push) Failing after 1s
Continuous Deployment / Deploy to Production (push) Has been cancelled
Continuous Deployment / Rollback Deployment (push) Has been cancelled
Continuous Deployment / Post-deployment Monitoring (push) Has been cancelled
Continuous Deployment / Notify Deployment Status (push) Has been cancelled
Continuous Deployment / Deploy to Staging (push) Has been cancelled
Security Scanning / Security Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:59:54 +08:00

222 lines
7.3 KiB
JavaScript

/**
* Module E — "Statistical Convergence Engine"
* RSSI waveform, person orbs, classification, fall alert, metric bars
*/
import * as THREE from 'three';
const WAVEFORM_POINTS = 120;
export class ConvergenceEngine {
constructor(scene, panelGroup) {
this.group = new THREE.Group();
if (panelGroup) panelGroup.add(this.group);
else scene.add(this.group);
// --- RSSI Waveform (scrolling line) ---
this._rssiHistory = new Float32Array(WAVEFORM_POINTS);
const waveGeo = new THREE.BufferGeometry();
this._wavePositions = new Float32Array(WAVEFORM_POINTS * 3);
for (let i = 0; i < WAVEFORM_POINTS; i++) {
this._wavePositions[i * 3] = (i / WAVEFORM_POINTS) * 6 - 3; // x: -3 to 3
this._wavePositions[i * 3 + 1] = 0;
this._wavePositions[i * 3 + 2] = 0;
}
waveGeo.setAttribute('position', new THREE.BufferAttribute(this._wavePositions, 3));
const waveMat = new THREE.LineBasicMaterial({
color: 0x00d4ff,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending,
});
this._waveform = new THREE.Line(waveGeo, waveMat);
this._waveform.position.y = 1.5;
this.group.add(this._waveform);
// Waveform glow (thicker, dimmer duplicate)
const glowMat = new THREE.LineBasicMaterial({
color: 0x00d4ff,
transparent: true,
opacity: 0.2,
linewidth: 2,
blending: THREE.AdditiveBlending,
});
this._waveGlow = new THREE.Line(waveGeo.clone(), glowMat);
this._waveGlow.position.y = 1.5;
this._waveGlow.scale.set(1, 1.3, 1);
this.group.add(this._waveGlow);
// --- Person orbs (up to 4) ---
this._personOrbs = [];
for (let i = 0; i < 4; i++) {
const orbGeo = new THREE.SphereGeometry(0.2, 16, 16);
const orbMat = new THREE.MeshBasicMaterial({
color: 0xff8800,
transparent: true,
opacity: 0,
blending: THREE.AdditiveBlending,
});
const orb = new THREE.Mesh(orbGeo, orbMat);
orb.position.set(-2 + i * 1.2, -0.5, 0);
this.group.add(orb);
const light = new THREE.PointLight(0xff8800, 0, 3);
orb.add(light);
this._personOrbs.push({ mesh: orb, light, mat: orbMat });
}
// --- Classification text sprite ---
this._classCanvas = document.createElement('canvas');
this._classCanvas.width = 256;
this._classCanvas.height = 48;
this._classCtx = this._classCanvas.getContext('2d');
this._classTex = new THREE.CanvasTexture(this._classCanvas);
const classMat = new THREE.SpriteMaterial({
map: this._classTex,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
this._classSprite = new THREE.Sprite(classMat);
this._classSprite.scale.set(3, 0.6, 1);
this._classSprite.position.y = 0.3;
this.group.add(this._classSprite);
// --- Fall alert ring ---
const alertGeo = new THREE.TorusGeometry(2.5, 0.05, 8, 48);
this._alertMat = new THREE.MeshBasicMaterial({
color: 0xff2244,
transparent: true,
opacity: 0,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
this._alertRing = new THREE.Mesh(alertGeo, this._alertMat);
this._alertRing.rotation.x = Math.PI / 2;
this._alertRing.position.y = -1;
this.group.add(this._alertRing);
// --- Metric bars (3: frame rate, confidence, variance) ---
this._metricBars = [];
const barLabels = ['CONF', 'VAR', 'SPEC'];
for (let i = 0; i < 3; i++) {
const barGeo = new THREE.PlaneGeometry(0.15, 1.5);
const barMat = new THREE.MeshBasicMaterial({
color: [0x00d4ff, 0x8844ff, 0xff8800][i],
transparent: true,
opacity: 0.5,
blending: THREE.AdditiveBlending,
depthWrite: false,
side: THREE.DoubleSide,
});
const bar = new THREE.Mesh(barGeo, barMat);
bar.position.set(2 + i * 0.4, -1.2, 0);
this.group.add(bar);
this._metricBars.push({ mesh: bar, mat: barMat });
}
this._rssiHead = 0;
this._lastClassification = '';
}
update(dt, elapsed, data) {
const features = data?.features || {};
const classification = data?.classification || {};
const persons = data?.persons || [];
const estPersons = data?.estimated_persons || 0;
// --- Update RSSI waveform ---
const rssi = features.mean_rssi || -50;
this._rssiHistory[this._rssiHead] = rssi;
this._rssiHead = (this._rssiHead + 1) % WAVEFORM_POINTS;
for (let i = 0; i < WAVEFORM_POINTS; i++) {
const histIdx = (this._rssiHead + i) % WAVEFORM_POINTS;
const val = this._rssiHistory[histIdx];
// Normalize RSSI (-80 to -20 range) to -1.5 to 1.5
this._wavePositions[i * 3 + 1] = ((val + 50) / 30) * 1.5;
}
this._waveform.geometry.attributes.position.needsUpdate = true;
// Copy to glow
const glowPos = this._waveGlow.geometry.attributes.position;
glowPos.array.set(this._wavePositions);
glowPos.needsUpdate = true;
// --- Person orbs ---
for (let i = 0; i < this._personOrbs.length; i++) {
const { mesh, light, mat } = this._personOrbs[i];
if (i < estPersons) {
mat.opacity = 0.7;
light.intensity = 1.0 + Math.sin(elapsed * 3 + i * 1.5) * 0.5;
const pulse = 1.0 + Math.sin(elapsed * 2 + i) * 0.15;
mesh.scale.set(pulse, pulse, pulse);
} else {
mat.opacity = 0.05;
light.intensity = 0;
mesh.scale.set(0.5, 0.5, 0.5);
}
}
// --- Classification text ---
const motionLevel = classification.motion_level || 'absent';
const label = motionLevel.toUpperCase().replace('_', ' ');
if (label !== this._lastClassification) {
this._lastClassification = label;
const ctx = this._classCtx;
ctx.clearRect(0, 0, 256, 48);
ctx.font = '600 24px "Courier New", monospace';
ctx.textAlign = 'center';
if (motionLevel === 'active') ctx.fillStyle = '#ff8800';
else if (motionLevel.includes('present')) ctx.fillStyle = '#00d4ff';
else ctx.fillStyle = '#445566';
ctx.fillText(label, 128, 32);
this._classTex.needsUpdate = true;
}
// --- Fall alert ---
const fallDetected = classification.fall_detected || false;
if (fallDetected) {
this._alertMat.opacity = 0.3 + Math.abs(Math.sin(elapsed * 6)) * 0.5;
const scale = 1.0 + Math.sin(elapsed * 4) * 0.1;
this._alertRing.scale.set(scale, scale, 1);
} else {
this._alertMat.opacity = 0;
}
// --- Metric bars ---
const confidence = classification.confidence || 0;
const variance = Math.min(1, (features.variance || 0) / 5);
const spectral = Math.min(1, (features.spectral_power || 0) / 0.5);
const values = [confidence, variance, spectral];
for (let i = 0; i < 3; i++) {
const bar = this._metricBars[i];
const v = values[i];
bar.mesh.scale.y = Math.max(0.05, v);
bar.mesh.position.y = -1.2 + v * 0.75;
bar.mat.opacity = 0.3 + v * 0.4;
}
}
dispose() {
this._waveform.geometry.dispose();
this._waveform.material.dispose();
this._waveGlow.geometry.dispose();
this._waveGlow.material.dispose();
this._alertRing.geometry.dispose();
this._alertMat.dispose();
this._classTex.dispose();
for (const { mesh, mat } of this._personOrbs) {
mesh.geometry.dispose();
mat.dispose();
}
for (const { mesh, mat } of this._metricBars) {
mesh.geometry.dispose();
mat.dispose();
}
}
}