memlnaut-nisps/playground/js/ui/auto-explore.js

161 lines
4 KiB
JavaScript
Raw Normal View History

feat(playground): implement Phases 2-4 of control surface spec Phase 2 — Pinning + History: - snapshot-stack.js: ring buffer (20 max) with auto-snapshot on train/randomize/thumbs-down, multi-level undo, tagged entries - ab-compare.js: A/B weight state comparison with capture/toggle/accept/revert - region-pin.js: pin rectangular input-space regions (Approach A: example pinning), pinned examples always included in training - param-pin.js: per-output pin flags, pin mask skips pinned nodes in moveWeights - phase2-ui.js: undo button with history popup, A/B toggle, long-press region pin, double-tap param pin - Modified mlp.js/iml.js/nisps-wasm.js to accept outputPinMask in moveWeights Phase 3 — Input Refinement + Exploration: - pressure-feedback.js: touch force + hold duration → intensity multiplier - auto-explore.js: automated thumbs-down at configurable interval, zoom-scaled - input-heatmap.js: 16×16 MLP sampling, 3 color modes (luminance/variance/ divergence), zoom-aware resampling, offscreen canvas rendering - phase3-ui.js: auto-explore toggle with progress ring, heatmap eye icon, pressure indicators, settings drawer section - joy-map-enhanced.js: added setHeatmap() for background layer rendering Phase 4 — Output Pipeline + Visualization + Polish: - output-pipeline.js: global curve → smoothing → slew rate → freeze gate - weight-health.js: weight magnitude histogram, dead/saturating/healthy status - gradient-flow.js: per-layer weight-delta analysis, vanishing/exploding detection - session-presets.js: save/load full state, URL sharing via compact params - phase4-ui.js: freeze button, network health panel, session preset UI All phases merged into a-app.js with proper integration: auto-snapshots, pressure-modulated RL, heatmap triggers, output pipeline in routeOutputs, gradient capture around training, persistence for all new state.
2026-03-26 09:48:12 +01:00
/**
* Auto-Explore automated thumbs-down at regular intervals.
*
* "Wander" mode for weight space: the system drifts continuously while
* the user only gives thumbs-up when it lands on something good.
*
* Respects noise cap, spread, zoom-aware feedback same rules as manual
* thumbs-down. Intensity scales with zoom: zoomed in = gentler steps,
* zoomed out = bigger leaps.
*
* @module auto-explore
*/
// ---- Constants ----
const MIN_INTERVAL = 0.5; // seconds
const MAX_INTERVAL = 10; // seconds
const DEFAULT_INTERVAL = 2; // seconds
const MIN_INTENSITY = 0.1;
const MAX_INTENSITY = 1.0;
const DEFAULT_INTENSITY = 0.5;
export class AutoExplore {
/**
* @param {object} [options]
* @param {number} [options.interval=2] - seconds between auto-steps
* @param {number} [options.intensity=0.5] - 0.1-1.0, scales noise per step
*/
constructor(options = {}) {
this._interval = clamp(options.interval ?? DEFAULT_INTERVAL, MIN_INTERVAL, MAX_INTERVAL);
this._intensity = clamp(options.intensity ?? DEFAULT_INTENSITY, MIN_INTENSITY, MAX_INTENSITY);
this._active = false;
this._elapsed = 0; // seconds since last auto-step
this._callback = null;
}
// ---- Control ----
/** Start auto-exploration. */
start() {
this._active = true;
this._elapsed = 0;
}
/** Stop auto-exploration. */
stop() {
this._active = false;
this._elapsed = 0;
}
/** Toggle on/off. */
toggle() {
if (this._active) this.stop();
else this.start();
}
/** Whether auto-explore is currently running. */
get active() {
return this._active;
}
// ---- Configuration ----
/**
* Set interval between auto-steps.
* @param {number} seconds - 0.5 to 10
*/
setInterval(seconds) {
this._interval = clamp(seconds, MIN_INTERVAL, MAX_INTERVAL);
}
/** @returns {number} */
getInterval() { return this._interval; }
/**
* Set exploration intensity.
* @param {number} intensity - 0.1 to 1.0
*/
setIntensity(intensity) {
this._intensity = clamp(intensity, MIN_INTENSITY, MAX_INTENSITY);
}
/** @returns {number} */
getIntensity() { return this._intensity; }
// ---- Tick (call every frame) ----
/**
* Advance the auto-explore timer. When the interval elapses, fires
* the registered callback with { intensity, zoomLevel }.
*
* @param {number} deltaTime - seconds since last frame
* @param {number} [zoomLevel=1.0] - current zoom level (0-1)
*/
tick(deltaTime, zoomLevel = 1.0) {
if (!this._active) return;
if (deltaTime <= 0) return;
this._elapsed += deltaTime;
if (this._elapsed >= this._interval) {
this._elapsed -= this._interval;
// Prevent runaway accumulation if tab was backgrounded
if (this._elapsed > this._interval) this._elapsed = 0;
this._fireExplore(zoomLevel);
}
}
// ---- Callback ----
/**
* Register the explore callback. Called when auto-step fires.
* @param {function} callback - receives { intensity }
*/
onExplore(callback) {
this._callback = callback;
}
// ---- Visual state ----
/**
* Progress toward next auto-step (0-1).
* Useful for a countdown/progress ring UI.
* @returns {number}
*/
getProgress() {
if (!this._active) return 0;
return Math.min(1, this._elapsed / this._interval);
}
// ---- Serialization ----
getConfig() {
return {
interval: this._interval,
intensity: this._intensity,
active: this._active,
};
}
setConfig(config) {
if (config.interval != null) this.setInterval(config.interval);
if (config.intensity != null) this.setIntensity(config.intensity);
// Note: we don't auto-start from config — caller decides
}
// ---- Internal ----
_fireExplore(zoomLevel) {
if (!this._callback) return;
// Scale intensity by zoom: zoomed in = gentler, zoomed out = bigger leaps
const scaledIntensity = this._intensity * Math.max(0.05, zoomLevel);
this._callback({ intensity: scaledIntensity });
}
}
// ---- Utility ----
function clamp(v, lo, hi) {
return v < lo ? lo : v > hi ? hi : v;
}