feat: v0.9.2 — power-of-two time scale: slider steps through the 1/2/4/8/16/32/64 ladder, POST /api/sim clamp widened to [1,64]

This commit is contained in:
2026-09-17 19:15:15 +00:00
parent 62f9929b51
commit 03653fc50e
12 changed files with 80 additions and 51 deletions
+13 -11
View File
@@ -1,22 +1,24 @@
// Pure log-scale mapping helpers for the time-scale slider (v0.9.1) — no game logic,
// unit-testable in node. Slider position t ∈ [0,1] ↔ scale = round(60^t), so equal
// distances on the slider correspond to equal multiplicative steps: 1× at t=0, ~8×
// mid-slider, 60× at t=1 (matching POST /api/sim's clamp range).
// Pure mapping helpers for the time-scale slider (v0.9.2) — no game logic, unit-testable
// in node. The slider has 7 discrete positions on a power-of-two ladder:
// 1× → 2× → 4× → 8× → 16× → 32× → 64×, so each step doubles the speed (matching
// POST /api/sim's clamp range [1, 64]).
export const MIN_SCALE = 1;
export const MAX_SCALE = 60;
export const TIME_SCALES = [1, 2, 4, 8, 16, 32, 64] as const;
export const MIN_SCALE: number = TIME_SCALES[0];
export const MAX_SCALE: number = TIME_SCALES[TIME_SCALES.length - 1];
const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
// t → scale. Exact at the endpoints: sliderToScale(0) === 1, sliderToScale(1) === 60.
// t → scale: the position maps to the nearest ladder step. Exact at the endpoints:
// sliderToScale(0) === 1, sliderToScale(1) === 64.
export function sliderToScale(t: number): number {
const tt = clamp(Number.isFinite(t) ? t : 0, 0, 1);
return Math.round(clamp(Math.pow(MAX_SCALE, tt), MIN_SCALE, MAX_SCALE));
return TIME_SCALES[clamp(Math.round(tt * (TIME_SCALES.length - 1)), 0, TIME_SCALES.length - 1)];
}
// scale → t (inverse of the pre-rounding mapping). Exact at the endpoints:
// scaleToSlider(1) === 0, scaleToSlider(60) === 1. Round-trips every integer 1..60.
// scale → t: the nearest ladder position by log2 distance. Exact at the endpoints:
// scaleToSlider(1) === 0, scaleToSlider(64) === 1; round-trips every value in TIME_SCALES.
export function scaleToSlider(scale: number): number {
const s = clamp(Number.isFinite(scale) ? scale : MIN_SCALE, MIN_SCALE, MAX_SCALE);
return Math.log(s) / Math.log(MAX_SCALE);
return Math.log2(s) / Math.log2(MAX_SCALE);
}