From 61f434a32db89bdc1b4cac411730364a5eaa73be Mon Sep 17 00:00:00 2001 From: Void Date: Tue, 10 Nov 2026 01:34:07 +0000 Subject: [PATCH] scene: two quick sky changes build the last sky; the Night drift test is exact A rebuild read the sky choice before awaiting its key and acted on it after, so Day then Dusk in quick succession left the Day scene live (the old test only counted disposals and passed with the Dusk scene never built). A rebuild now stands down if the choice moved while it awaited; that change has its own rebuild. The test asserts the last sky built is the last one chosen, is the live scene (a login reaches it), and that every other scene was disposed. The NIGHT_SKY_LIGHTEST drift test compared with a 1e-6 tolerance, so 0.081641 passed; it is now exact equality. Co-Authored-By: Claude Sonnet 5.5 --- src/app/features/scene/index.test.ts | 21 +++++++++++++++++---- src/app/features/scene/index.ts | 2 ++ src/app/features/scene/skyTheme.test.ts | 2 +- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/app/features/scene/index.test.ts b/src/app/features/scene/index.test.ts index 5d0e6410..6a29b33f 100644 --- a/src/app/features/scene/index.test.ts +++ b/src/app/features/scene/index.test.ts @@ -4,6 +4,7 @@ import { baked } from '../../../sky/baked'; import type { Scene } from './Scene'; import { startBackgroundScene } from './index'; import { setSkyClock, setSkyState } from './skyState'; +import { ridgeSkyFor } from './skyForState'; const { createRidgeScene, createScene } = vi.hoisted(() => ({ createRidgeScene: vi.fn(), @@ -155,16 +156,28 @@ describe('startBackgroundScene follows the sky state', () => { expect(createRidgeScene).toHaveBeenCalledOnce(); }); - it('keeps only the latest of two quick changes', async () => { + it('keeps only the latest of two quick changes: the last sky is built, the others are not left running', async () => { const scenes = [fakeScene(), fakeScene(), fakeScene()]; scenes.forEach((s) => createRidgeScene.mockReturnValueOnce(s)); const scene = await startBackgroundScene(config, caps, { canvas }); setSkyState('day'); - setSkyState('night'); + setSkyState('dusk'); await flush(); - expect(scenes[1].dispose).toHaveBeenCalledOnce(); - expect(scenes[2].dispose).not.toHaveBeenCalled(); + // The sky last built is dusk's, and it is the live scene: a login reaches it and nothing else. + const built = createRidgeScene.mock.calls.length; + expect(hex(createRidgeScene.mock.calls[built - 1][0].sky.top)).toBe( + hex(ridgeSkyFor('dusk').top) + ); + const live = scenes[built - 1]; + scene?.login(); + expect(live.login).toHaveBeenCalledOnce(); + scenes.filter((s) => s !== live).forEach((s) => expect(s.login).not.toHaveBeenCalled()); + // Every scene but the live one was disposed, the live one not yet. + scenes.slice(0, built).forEach((s) => { + expect(s.dispose).toHaveBeenCalledTimes(s === live ? 0 : 1); + }); scene?.dispose(); + expect(live.dispose).toHaveBeenCalledOnce(); }); it('leaves the ray-march scene alone: it has one fixed dusk', async () => { diff --git a/src/app/features/scene/index.ts b/src/app/features/scene/index.ts index 55966188..73377440 100644 --- a/src/app/features/scene/index.ts +++ b/src/app/features/scene/index.ts @@ -95,8 +95,10 @@ export async function startBackgroundScene( let generation = 0; // A rebuild is for a different sky ('auto' crossing a boundary, a new choice), not for a repeat of the one drawn. const rebuild = async () => { + // The choice can move while the key is awaited; that change has its own rebuild, so this one stands down. const next = getSkyState(); const key = await skyKey(config, next); + if (getSkyState() !== next) return; if ((key !== null && key === built) || disposed) return; generation += 1; const mine = generation; diff --git a/src/app/features/scene/skyTheme.test.ts b/src/app/features/scene/skyTheme.test.ts index 39b74fdc..53cf2873 100644 --- a/src/app/features/scene/skyTheme.test.ts +++ b/src/app/features/scene/skyTheme.test.ts @@ -92,7 +92,7 @@ describe('ink of the theme a state picks, on bare sky', () => { }); it('keeps the lightest Night sky the link was fitted to equal to the Night backdrop (no drift)', () => { - expect(Math.abs(NIGHT_SKY_LIGHTEST - skyBackdrop('night').lightest)).toBeLessThan(1e-6); + expect(NIGHT_SKY_LIGHTEST).toBe(skyBackdrop('night').lightest); }); it.each([ -- 2.47.3