spb/earth-now Public License
earth-now.co — real-time planetary dashboard: live world metrics modeled, not streamed.
TypeScript 93%
Shell 2.3%
SQL 1.4%
JavaScript 1.3%
Dockerfile 1.2%
CSS 0.8%
1/**2 * earth-now.co3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: apps/web/lib/smoothing.ts6 * Purpose: Pure 60 s linear blend between an old and a new CounterModel — no visible jump when SSE delivers a refit (time is a parameter)7 */89import { counterValue, type CounterModel } from "@earth-now/counter";1011/** Guardrail duration: a model swap is smoothed over 60 s (CLAUDE.md production rule). */12export const SMOOTHING_DURATION_MS = 60_000;1314/** Blend weight for the new model at time t: 0 before swapStart, 1 after swapStart+duration. */15export function smoothingAlpha(16 tMs: number,17 swapStartMs: number,18 durationMs: number = SMOOTHING_DURATION_MS,19): number {20 if (durationMs <= 0 || tMs >= swapStartMs + durationMs) return 1;21 if (tMs <= swapStartMs) return 0;22 return (tMs - swapStartMs) / durationMs;23}2425/** Linear blend of two already-evaluated values across the smoothing window. */26export function blendValues(27 oldValue: number,28 newValue: number,29 tMs: number,30 swapStartMs: number,31 durationMs: number = SMOOTHING_DURATION_MS,32): number {33 const alpha = smoothingAlpha(tMs, swapStartMs, durationMs);34 if (alpha === 0) return oldValue;35 if (alpha === 1) return newValue;36 return oldValue * (1 - alpha) + newValue * alpha;37}3839/**40 * Evaluator that linearly cross-fades counterValue(oldModel) → counterValue(newModel)41 * over [swapStartMs, swapStartMs + durationMs]. Exactly the old value at swapStart,42 * exactly the new value from swapStart+duration onwards. Pure — never reads the clock.43 */44export function makeSmoothedEvaluator(45 oldModel: CounterModel,46 newModel: CounterModel,47 swapStartMs: number,48 durationMs: number = SMOOTHING_DURATION_MS,49): (tMs: number) => number {50 return (tMs: number): number => {51 const alpha = smoothingAlpha(tMs, swapStartMs, durationMs);52 if (alpha === 1) return counterValue(newModel, tMs);53 const oldValue = counterValue(oldModel, tMs);54 if (alpha === 0) return oldValue;55 return oldValue * (1 - alpha) + counterValue(newModel, tMs) * alpha;56 };57}58