/** * SensorSamplingTrait — domain-neutral signal acquisition with noise models * and ring-buffer windowing (H2 'sensor' family). * * Sensors are identified by string IDs. Each sensor has a configurable noise * model applied to incoming raw readings before buffering. A fixed-capacity * ring buffer stores the last N readings per sensor, enabling simple windowed * statistics (mean, variance, min, max) without external state management. * * ## Noise models * * - `'none'` — raw value passed through unchanged. * - `'gaussian'` — additive Gaussian noise N(0, σ²) via Box-Muller. * - `'uniform'` — additive uniform noise U(−range, +range). * - `'quantized'` — round to nearest `step`, then optional gaussian. * * ## Domain-neutrality * * No domain nouns appear here. The same trait covers: * - Robotics: joint-angle encoders, IMU, force/torque sensors. * - IoT: temperature, pressure, humidity, flow. * - Scientific: spectral intensity, particle count, magnetic field. * - Quantum: qubit readout (binary sensor, bit-flip noise via bernoulli). * * ## Trait usage * * onAttach: initialises per-sensor ring buffers from config. * onEvent 'sensor.record': record a raw reading → apply noise → buffer → * write result to node.__sensor_last[sensorId]. * onEvent 'sensor.read_window': compute windowed stats for a sensor → * write to node.__sensor_window[sensorId]. * onEvent 'sensor.reset': clear all buffers. */ import type { TraitHandler } from './TraitTypes'; /** Gaussian noise: additive N(0, σ²). */ export interface GaussianNoise { type: 'gaussian'; /** Standard deviation σ. */ sigma: number; } /** Uniform noise: additive U(−range, +range). */ export interface UniformNoise { type: 'uniform'; /** Half-width of the uniform distribution. */ range: number; } /** * Quantization noise: round to nearest `step`, then optional Gaussian. * Models ADC quantization (and binary sensors when step=1). */ export interface QuantizedNoise { type: 'quantized'; /** Quantization step (e.g. 0.01 for a 2-decimal sensor). */ step: number; /** Optional residual Gaussian σ after quantization. Default: 0. */ residualSigma?: number; } /** No noise — raw value passed through. */ export interface NoNoise { type: 'none'; } export type NoiseModel = GaussianNoise | UniformNoise | QuantizedNoise | NoNoise; /** Configuration for one sensor channel. */ export interface SensorChannelConfig { /** Unique sensor identifier. */ id: string; /** Human-readable unit label (e.g. 'm/s²', '°C', 'rad'). Not interpreted. */ unit?: string; /** Noise model applied to incoming raw readings. Default: none. */ noise?: NoiseModel; /** Ring-buffer capacity (number of readings to keep). Default: 32. */ bufferSize?: number; } /** Trait configuration — list of sensor channels. */ export interface SensorSamplingConfig { /** Sensor channels to initialise. */ sensors: SensorChannelConfig[]; } /** A raw + noisy reading for one sensor. */ export interface SensorReading { /** Sensor identifier. */ sensorId: string; /** Raw (pre-noise) value. */ rawValue: number; /** Value after noise application. */ noisyValue: number; /** Monotonic timestamp (caller-supplied, e.g. simulation tick). */ timestamp: number; } /** Windowed statistics over a sensor's ring buffer. */ export interface SensorWindowResult { sensorId: string; /** All buffered readings (oldest → newest). */ readings: ReadonlyArray; /** Number of readings in the buffer. */ count: number; /** Arithmetic mean of noisyValue. NaN if buffer empty. */ mean: number; /** Sample variance of noisyValue. NaN if fewer than 2 readings. */ variance: number; /** Minimum noisyValue. Infinity if buffer empty. */ min: number; /** Maximum noisyValue. -Infinity if buffer empty. */ max: number; } /** Payload for `sensor.record` event. */ export interface SensorRecordRequest { /** Target sensor. If omitted and config has exactly one sensor, uses that. */ sensorId?: string; /** Raw measured value. */ rawValue: number; /** Monotonic timestamp. Default: 0. */ timestamp?: number; /** Override noise model for this recording only. */ noise?: NoiseModel; } /** Payload for `sensor.read_window` event. */ export interface SensorReadWindowRequest { /** Target sensor. If omitted, uses the first configured sensor. */ sensorId?: string; } /** * Box-Muller transform: generates one N(0,1) sample using two U(0,1) inputs. * Pure function — callers supply the uniform random values to enable testing. */ export declare function boxMuller(u1: number, u2: number): number; /** * Apply a noise model to a raw value. Uses `Math.random()` for stochastic * models — determinism in tests can be achieved by mocking Math.random. */ export declare function applyNoise(raw: number, noise: NoiseModel): number; /** Fixed-capacity circular buffer of SensorReadings. */ export declare class SensorRingBuffer { readonly capacity: number; private readonly buf; private head; private _size; constructor(capacity: number); push(r: SensorReading): void; get size(): number; /** Returns readings oldest-first. */ toArray(): SensorReading[]; latest(): SensorReading | undefined; clear(): void; } /** * Record one reading: apply noise, return the SensorReading. * Callers are responsible for buffering (the TraitHandler does this via the * node's internal buffer map). */ export declare function recordReading(rawValue: number, noise: NoiseModel, sensorId: string, timestamp: number): SensorReading; /** Compute windowed statistics over a ring buffer's current contents. */ export declare function computeWindowStats(sensorId: string, buffer: SensorRingBuffer): SensorWindowResult; export declare const sensorSamplingHandler: TraitHandler;