diff --git a/app/games/otter-stop/game.js b/app/games/otter-stop/game.js index 43f7945..1f6aad9 100644 --- a/app/games/otter-stop/game.js +++ b/app/games/otter-stop/game.js @@ -49,8 +49,12 @@ export function setGoKeys(keys) { /** Display interval at level 0 (milliseconds). */ const BASE_INTERVAL_MS = 1500; -/** How much the interval shrinks per level (milliseconds). */ -const INTERVAL_STEP_MS = 50; +/** + * Geometric decay factor applied to the display interval per level. + * Each level multiplies the current interval by this rate, producing large + * speed jumps at low levels that taper off as the game becomes faster. + */ +const INTERVAL_DECAY_RATE = 0.88; /** Minimum allowed display interval (milliseconds). */ const MIN_INTERVAL_MS = 150; @@ -263,12 +267,14 @@ export function recordResponse(isNoGo, spacePressed) { /** * Return the display interval in milliseconds for the current level. - * Starts at 700 ms and decreases by 50 ms per level, with a floor of 150 ms. + * Uses geometric decay: each level multiplies the base interval by + * INTERVAL_DECAY_RATE, producing large speed jumps early and increasingly + * smaller increments as the game gets faster. Floored at MIN_INTERVAL_MS. * * @returns {number} Display interval in milliseconds. */ export function getCurrentIntervalMs() { - return Math.max(BASE_INTERVAL_MS - level * INTERVAL_STEP_MS, MIN_INTERVAL_MS); + return Math.max(Math.round(BASE_INTERVAL_MS * (INTERVAL_DECAY_RATE ** level)), MIN_INTERVAL_MS); } // ── Getters ─────────────────────────────────────────────────────────────────── diff --git a/app/games/otter-stop/style.css b/app/games/otter-stop/style.css index 85d70f5..f84c2d7 100644 --- a/app/games/otter-stop/style.css +++ b/app/games/otter-stop/style.css @@ -69,8 +69,8 @@ } .os-stimulus__img { - max-width: min(440px, calc(50vmin - 60px)); - max-height: min(440px, calc(50vmin - 60px)); + width: 100%; + height: 100%; object-fit: contain; transition: opacity 0.08s ease; pointer-events: none; diff --git a/app/games/otter-stop/tests/game.test.js b/app/games/otter-stop/tests/game.test.js index 4c12d8f..e731c38 100644 --- a/app/games/otter-stop/tests/game.test.js +++ b/app/games/otter-stop/tests/game.test.js @@ -510,16 +510,16 @@ describe('getCurrentIntervalMs()', () => { expect(getCurrentIntervalMs()).toBe(1500); }); - it('returns 1450 at level 1 (after 3 correct no-go inhibitions)', () => { + it('returns 1320 at level 1 (after 3 correct no-go inhibitions)', () => { recordResponse(true, false); recordResponse(true, false); recordResponse(true, false); - expect(getCurrentIntervalMs()).toBe(1450); + expect(getCurrentIntervalMs()).toBe(1320); // Math.round(1500 * 0.88) }); it('returns 150 at a high level (floor clamped)', () => { - // 3 no-go correct per level, need level 27 → (1500 - 27*50 = 150) - for (let i = 0; i < 81; i += 1) recordResponse(true, false); + // 3 no-go correct per level; floor is reached at level 19 (Math.round(1500 * 0.88^19) < 150) + for (let i = 0; i < 57; i += 1) recordResponse(true, false); expect(getCurrentIntervalMs()).toBe(150); });