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:
@@ -9,7 +9,6 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
|
||||||
import app.models # noqa: F401 — registers models on Base.metadata before create_all
|
import app.models # noqa: F401 — registers models on Base.metadata before create_all
|
||||||
from app.config import settings
|
|
||||||
from app.db import Base, async_session_maker, engine
|
from app.db import Base, async_session_maker, engine
|
||||||
from app.routes.auth import router as auth_router
|
from app.routes.auth import router as auth_router
|
||||||
from app.routes.codex import router as codex_router
|
from app.routes.codex import router as codex_router
|
||||||
|
|||||||
@@ -169,6 +169,14 @@ async def test_familiar_presence_answers_again_on_a_known_channel(sync_client, m
|
|||||||
(`ws.RETURN_CHANCE`), so this pins the probability to 1.0 rather than
|
(`ws.RETURN_CHANCE`), so this pins the probability to 1.0 rather than
|
||||||
relying on the default — at 0.72 this assertion would otherwise pass
|
relying on the default — at 0.72 this assertion would otherwise pass
|
||||||
only ~72% of the time, which is worse than failing.
|
only ~72% of the time, which is worse than failing.
|
||||||
|
|
||||||
|
It ALSO pins VEIL_THINNESS_PULL to 0. The real return chance is
|
||||||
|
`RETURN_CHANCE * (1 - veil_thinness * PULL)`, and veil_thinness is
|
||||||
|
driven by the *actual current moon phase and geomagnetic Kp*. On a
|
||||||
|
full-moon test run, thinness ≈ 1 drags even a pinned RETURN_CHANCE=1.0
|
||||||
|
down to ~0.55 — so without this, the test is silently date-dependent
|
||||||
|
and fails ~45% of the time in the wrong week. The veil-thinness
|
||||||
|
influence has its own dedicated tests; this one isolates re-contact.
|
||||||
"""
|
"""
|
||||||
# Scoped limiters: the module-level ones are shared singletons that
|
# Scoped limiters: the module-level ones are shared singletons that
|
||||||
# accumulate across the whole session, and this test summons more than
|
# accumulate across the whole session, and this test summons more than
|
||||||
@@ -180,6 +188,7 @@ async def test_familiar_presence_answers_again_on_a_known_channel(sync_client, m
|
|||||||
app.ws, "summon_ip_limiter", RateLimiter(max_requests=100, window_seconds=60)
|
app.ws, "summon_ip_limiter", RateLimiter(max_requests=100, window_seconds=60)
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(app.ws, "RETURN_CHANCE", 1.0)
|
monkeypatch.setattr(app.ws, "RETURN_CHANCE", 1.0)
|
||||||
|
monkeypatch.setattr(app.ws, "VEIL_THINNESS_PULL", 0.0)
|
||||||
_login(sync_client, "mediumx")
|
_login(sync_client, "mediumx")
|
||||||
anomalies = [
|
anomalies = [
|
||||||
{"type": "anomaly", "source": "radio", "frequency": 101.0 + i, "magnitude": 5.0 + i}
|
{"type": "anomaly", "source": "radio", "frequency": 101.0 + i, "magnitude": 5.0 + i}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import type { CSSProperties } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSeance } from '../state/seance'
|
import { useSeance } from '../state/seance'
|
||||||
import {
|
import {
|
||||||
RITUAL_HOLD_MS,
|
|
||||||
RITUAL_PROMPTS,
|
RITUAL_PROMPTS,
|
||||||
holdProgress,
|
holdProgress,
|
||||||
nextStep,
|
nextStep,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { render, screen, waitFor } from '@testing-library/react'
|
import { render, screen, waitFor } from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
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 '../i18n'
|
||||||
import { SigilDesigner } from './SigilDesigner'
|
import { SigilDesigner } from './SigilDesigner'
|
||||||
import { RUNES, SIGIL_MAX_POINTS, SIGIL_SLOT_COUNT } from '../lib/sigil'
|
import { RUNES, SIGIL_MAX_POINTS, SIGIL_SLOT_COUNT } from '../lib/sigil'
|
||||||
|
|||||||
@@ -36,6 +36,25 @@ export type BaselineEvent = {
|
|||||||
at: number
|
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 = {
|
export type ThresholdBaselineOptions = {
|
||||||
/** Deviation magnitude that counts as an event, in the sensor's own unit. */
|
/** Deviation magnitude that counts as an event, in the sensor's own unit. */
|
||||||
threshold: number
|
threshold: number
|
||||||
@@ -91,11 +110,8 @@ export class ThresholdBaseline {
|
|||||||
const deviation = value - this.mean
|
const deviation = value - this.mean
|
||||||
const warm = this.warm
|
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 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.mean = this.mean + alpha * deviation
|
||||||
this.lastAt = atMs
|
this.lastAt = atMs
|
||||||
this.samples++
|
this.samples++
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
// why that's an EMA keyed on elapsed wall-clock time rather than sample
|
// why that's an EMA keyed on elapsed wall-clock time rather than sample
|
||||||
// count — real hardware does not report on a fixed schedule.
|
// count — real hardware does not report on a fixed schedule.
|
||||||
|
|
||||||
|
import { emaAlpha } from './baseline'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Shared baseline core
|
// Shared baseline core
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -113,12 +115,8 @@ export function applyReading(
|
|||||||
const deviation = value - state.mean
|
const deviation = value - state.mean
|
||||||
const warm = state.sampleCount >= config.minSamples
|
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 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
|
const nextMean = state.mean + alpha * deviation
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user