chore: dead-code + duplicate sweep; fix moon-flaky re-contact test
Dead code: removed an unused `settings` import (main.py), an unused `RITUAL_HOLD_MS` import (RitualPanel), and an unused `beforeEach` (SigilDesigner test). The `_refs` keep-alive in sdr.ts is deliberate (holds hardware-pass constants) and stays; the orphaned .evp-scope / .radio-waterfall CSS was already removed in an earlier lint pass. Duplicate: coldSpot.ts and baseline.ts each carried the same time-aware EMA alpha formula. Extracted it as baseline.emaAlpha(dtMs, tauMs) and pointed both at it. coldSpot's pure-function core is deliberately NOT merged into the stateful ThresholdBaseline class — different contract (immutable-state-threaded vs internal-threshold), and forcing them together would be an overhaul that risks the tested cold-spot logic. Determinism fix: test_familiar_presence_answers_again_on_a_known_channel pinned RETURN_CHANCE=1.0 but not the sky. Since the astronomy wiring made the real return chance RETURN_CHANCE*(1 - veil_thinness*PULL), and veil_thinness reads the *actual current moon phase*, a full-moon test run dragged the effective chance to ~0.55 and the test failed ~45% of the time. Now also pins VEIL_THINNESS_PULL=0 to isolate re-contact from the veil influence (which has its own tests). Verified 12/12 consecutive passes; it was ~7/12 before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,6 @@ import type { CSSProperties } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSeance } from '../state/seance'
|
||||
import {
|
||||
RITUAL_HOLD_MS,
|
||||
RITUAL_PROMPTS,
|
||||
holdProgress,
|
||||
nextStep,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import '../i18n'
|
||||
import { SigilDesigner } from './SigilDesigner'
|
||||
import { RUNES, SIGIL_MAX_POINTS, SIGIL_SLOT_COUNT } from '../lib/sigil'
|
||||
|
||||
@@ -36,6 +36,25 @@ export type BaselineEvent = {
|
||||
at: number
|
||||
}
|
||||
|
||||
/**
|
||||
* EMA smoothing factor from elapsed wall-clock time, not sample count.
|
||||
*
|
||||
* Sensor intervals here are irregular — a BLE beacon may advertise every
|
||||
* 100ms or every 2s, a device can drop offline and reconnect — so a fixed
|
||||
* per-sample alpha would weight a burst and a long gap identically. The
|
||||
* exponential-decay form degrades gracefully at both ends: a long gap
|
||||
* pushes alpha toward 1 (the old baseline is stale, trust the new reading),
|
||||
* a rapid burst toward 0 (barely move it).
|
||||
*
|
||||
* A non-positive dt (duplicate timestamps, clock skew, two readings in one
|
||||
* tick) can't be trusted as an elapsed time, so it falls back to a flat
|
||||
* step rather than dividing by it. Shared by ThresholdBaseline and
|
||||
* coldSpot's pure-function core so the one formula lives in one place.
|
||||
*/
|
||||
export function emaAlpha(dtMs: number, tauMs: number): number {
|
||||
return dtMs > 0 ? 1 - Math.exp(-dtMs / tauMs) : 0.15
|
||||
}
|
||||
|
||||
export type ThresholdBaselineOptions = {
|
||||
/** Deviation magnitude that counts as an event, in the sensor's own unit. */
|
||||
threshold: number
|
||||
@@ -91,11 +110,8 @@ export class ThresholdBaseline {
|
||||
const deviation = value - this.mean
|
||||
const warm = this.warm
|
||||
|
||||
// Guard against zero/negative dt (duplicate timestamps, clock skew, two
|
||||
// readings in one tick) with a flat fallback rather than dividing by an
|
||||
// elapsed time that isn't trustworthy.
|
||||
const dt = this.lastAt === null ? 0 : atMs - this.lastAt
|
||||
const alpha = dt > 0 ? 1 - Math.exp(-dt / this.opts.tauMs) : 0.15
|
||||
const alpha = emaAlpha(dt, this.opts.tauMs)
|
||||
this.mean = this.mean + alpha * deviation
|
||||
this.lastAt = atMs
|
||||
this.samples++
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
// why that's an EMA keyed on elapsed wall-clock time rather than sample
|
||||
// count — real hardware does not report on a fixed schedule.
|
||||
|
||||
import { emaAlpha } from './baseline'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared baseline core
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -113,12 +115,8 @@ export function applyReading(
|
||||
const deviation = value - state.mean
|
||||
const warm = state.sampleCount >= config.minSamples
|
||||
|
||||
// Guard against zero/negative/out-of-order dt (duplicate timestamps,
|
||||
// clock skew, or two readings racing in the same tick) with a flat
|
||||
// fallback step rather than dividing by an elapsed time that isn't
|
||||
// trustworthy.
|
||||
const dtMs = state.lastAt === null ? 0 : atMs - state.lastAt
|
||||
const alpha = dtMs > 0 ? 1 - Math.exp(-dtMs / config.tauMs) : 0.15
|
||||
const alpha = emaAlpha(dtMs, config.tauMs)
|
||||
const nextMean = state.mean + alpha * deviation
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user