mirror of
https://github.com/ruvnet/RuView
synced 2026-08-02 19:11:46 +00:00
feat(dashboard): App Store runtime — 6 simulated apps emit real events live
Closes the "do the App Store toggles actually do anything?" question:
they now do, for the subset of apps whose algorithms map onto nvsim's
magnetic frame stream as a proxy for their native CSI input.
## New: AppManifest.runtime field
Three values:
- `running` — algorithm genuinely runs in browser (just nvsim today)
- `simulated` — pared-down version against nvsim's B-field stream
- `mesh-only` — needs ESP32-S3 + WS transport (deferred to V2)
Visible in the App Store as a colored badge on every card with hover
tooltip explaining what activation actually does.
## New: appRuntimes.ts — 6 in-browser simulated runtimes
- `vital_trend` — peak-detect on B_z oscillation → 1 Hz HR/BR
events 100/101/102/103/104 + bradycardia/tachypnea
- `occupancy` — variance threshold on |B| → 300/302
- `intrusion` — |B| > 1.5× ambient + 0.5 s dwell → 200
- `coherence` — recent vs baseline z-score → 2
- `adversarial` — log-jump anomaly in |B| → 3
- `exo_ghost_hunter`— impulsive/drift/random anomaly classification → 651
Each receives an AppRuntimeContext (frame, |B|, history, elapsed-time,
per-app scratch state) and emits real i32 event IDs matching the
event_types mod in wifi-densepose-wasm-edge.
## Runtime dispatcher in main.ts
On every MagFrameBatch from the worker, iterate over activeAppIds.
For each id with a registered runtime, call the runtime fn with the
context, push any returned events into appEvents + the console feed.
mesh-only apps no-op silently (their toggle still persists for the
WS transport).
## App Store UI
- Per-card runtime badge (running / simulated / mesh-only) with tooltip
- "Live runtime feed" panel above the grid: shows last 12 emitted
events with timestamp, app id, event name + i32 id, detail
- Active simulated-app counter: "5 simulated apps active"
- Per-card event counter "⚡ N ev" once events arrive
- Toggle log line includes runtime mode: "live runtime engaged" /
"queued (needs ESP32 mesh)"
Validated end-to-end on https://ruvnet.github.io/RuView/nvsim/ — toggled
{vital_trend, occupancy, intrusion, coherence}, pressed Run, the feed
filled with real events: COHERENCE_SCORE z=0.87 stable, VITAL_TREND
HR=40 BPM BR=10, BRADYCARDIA, BRADYPNEA. Console log mirrors with
[appId] prefix. Zero browser errors.
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
+42
-2
@@ -9,7 +9,9 @@ import {
|
||||
pushLog, expectedWitness, framesEmitted, fps, lastB, bMag,
|
||||
pushTrace, pushStripBar, lastFrame, sceneJson, witnessHex,
|
||||
replHistory, scenePositions, type SceneItemPos,
|
||||
activeAppIds, pushAppEvent,
|
||||
} from './store/appStore';
|
||||
import { APP_RUNTIMES, type AppRuntimeContext } from './store/appRuntimes';
|
||||
import { kvGet, kvSet } from './store/persistence';
|
||||
|
||||
function applyTheme(t: string): void {
|
||||
@@ -59,6 +61,11 @@ function applyMotion(reduced: boolean): void {
|
||||
}
|
||||
});
|
||||
|
||||
// Per-app runtime scratch state + history buffer.
|
||||
const appState: Record<string, Record<string, number>> = {};
|
||||
const bMagHistory: number[] = [];
|
||||
const runtimeStartTs = performance.now();
|
||||
|
||||
client.onFrames((batch) => {
|
||||
if (batch.frames.length === 0) return;
|
||||
const last = batch.frames[batch.frames.length - 1];
|
||||
@@ -67,11 +74,44 @@ function applyMotion(reduced: boolean): void {
|
||||
const by = last.bPt[1] * 1e-12;
|
||||
const bz = last.bPt[2] * 1e-12;
|
||||
lastB.value = [bx, by, bz];
|
||||
bMag.value = Math.sqrt(bx * bx + by * by + bz * bz);
|
||||
// For trace display we use nT scale.
|
||||
const bmagT = Math.sqrt(bx * bx + by * by + bz * bz);
|
||||
bMag.value = bmagT;
|
||||
pushTrace([bx * 1e9, by * 1e9, bz * 1e9]);
|
||||
const amp = Math.min(1, Math.abs(bz * 1e9) / 5 + 0.3);
|
||||
pushStripBar(amp);
|
||||
|
||||
bMagHistory.push(bmagT);
|
||||
while (bMagHistory.length > 256) bMagHistory.shift();
|
||||
|
||||
// Dispatch the frame to every active simulated app runtime.
|
||||
const activeIds = activeAppIds.value;
|
||||
if (activeIds.size === 0) return;
|
||||
const elapsedS = (performance.now() - runtimeStartTs) / 1000;
|
||||
for (const id of activeIds) {
|
||||
const fn = APP_RUNTIMES[id];
|
||||
if (!fn) continue; // mesh-only apps — toggle persists, no in-browser runtime
|
||||
if (!appState[id]) appState[id] = {};
|
||||
const ctx: AppRuntimeContext = {
|
||||
frame: last,
|
||||
bMagT: bmagT,
|
||||
bRecoveredT: [bx, by, bz],
|
||||
bHistory: bMagHistory,
|
||||
elapsedS,
|
||||
state: appState[id],
|
||||
};
|
||||
try {
|
||||
const result = fn(ctx);
|
||||
if (!result) continue;
|
||||
const evs = Array.isArray(result) ? result : [result];
|
||||
for (const ev of evs) {
|
||||
pushAppEvent(ev);
|
||||
pushLog('info',
|
||||
`<span class="k">[${ev.appId}]</span> <span class="s">${ev.eventName}</span> <span class="n">(${ev.eventId})</span>${ev.detail ? ' · ' + ev.detail : ''}`);
|
||||
}
|
||||
} catch (e) {
|
||||
pushLog('warn', `[${id}] runtime error: ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user