feat: daybreak finale turns the contact arrival into a sunrise with a dawn chord
This commit is contained in:
79
tests/unit/daybreak.test.ts
Normal file
79
tests/unit/daybreak.test.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { DAWN_STEPS, dawnStep, END_OF_SHIFT_SUBLINE } from '@/journey/dawn';
|
||||
import {
|
||||
blendDaybreak,
|
||||
CHAPTER_PALETTES,
|
||||
createMutableChapterPalette,
|
||||
DAYBREAK_PALETTE,
|
||||
engineOffStarScale,
|
||||
} from '@/scene/world/palette';
|
||||
|
||||
describe('dawn step mapping (html[data-dawn])', () => {
|
||||
it('steps night -> first-light -> sunrise -> day at 0.3 / 0.6 / 0.95', () => {
|
||||
expect(DAWN_STEPS).toEqual({ 'first-light': 0.3, sunrise: 0.6, day: 0.95 });
|
||||
const cases: Array<[number, string]> = [
|
||||
[0, 'night'],
|
||||
[0.29, 'night'],
|
||||
[0.3, 'first-light'],
|
||||
[0.59, 'first-light'],
|
||||
[0.6, 'sunrise'],
|
||||
[0.949, 'sunrise'],
|
||||
[0.95, 'day'],
|
||||
[1, 'day'],
|
||||
[-1, 'night'],
|
||||
[Number.NaN, 'night'],
|
||||
];
|
||||
for (const [dawn, step] of cases) expect(dawnStep(dawn)).toBe(step);
|
||||
});
|
||||
|
||||
it('reads the end-of-shift sub-line', () => {
|
||||
expect(END_OF_SHIFT_SUBLINE).toBe('END OF SHIFT · 05:48');
|
||||
});
|
||||
});
|
||||
|
||||
describe('daybreak palette', () => {
|
||||
it('is not a journey chapter', () => {
|
||||
expect(Object.keys(CHAPTER_PALETTES)).not.toContain('daybreak');
|
||||
});
|
||||
|
||||
it('leaves the destination untouched at 0 and becomes daybreak at 1', () => {
|
||||
const untouched = createMutableChapterPalette('destination');
|
||||
blendDaybreak(untouched, 0);
|
||||
expect(untouched.sky.getHex()).toBe(CHAPTER_PALETTES.destination.sky);
|
||||
expect(untouched.starVisibility).toBe(CHAPTER_PALETTES.destination.starVisibility);
|
||||
|
||||
const full = createMutableChapterPalette('destination');
|
||||
blendDaybreak(full, 1);
|
||||
for (const key of ['sky', 'fog', 'terrainLow', 'terrainHigh', 'sun', 'haze'] as const) {
|
||||
expect(full[key].getHex()).toBe(DAYBREAK_PALETTE[key]);
|
||||
}
|
||||
expect(full.warmth).toBe(1.25);
|
||||
expect(full.terrainEnergy).toBe(0.4);
|
||||
expect(full.starVisibility).toBe(0);
|
||||
expect(full.streakEnergy).toBe(0);
|
||||
});
|
||||
|
||||
it('blends partway and clamps out-of-range amounts', () => {
|
||||
const half = createMutableChapterPalette('destination');
|
||||
blendDaybreak(half, 0.5);
|
||||
expect(half.warmth).toBeCloseTo((1 + 1.25) / 2);
|
||||
expect(half.starVisibility).toBeCloseTo(0.08);
|
||||
const over = createMutableChapterPalette('destination');
|
||||
blendDaybreak(over, 3);
|
||||
expect(over.sky.getHex()).toBe(DAYBREAK_PALETTE.sky);
|
||||
const nan = createMutableChapterPalette('destination');
|
||||
blendDaybreak(nan, Number.NaN);
|
||||
expect(nan.sky.getHex()).toBe(CHAPTER_PALETTES.destination.sky);
|
||||
});
|
||||
|
||||
it('boosts the stars as the engine cuts, then drains them as the dawn rises', () => {
|
||||
expect(engineOffStarScale(0, 0)).toBe(1);
|
||||
expect(engineOffStarScale(1, 0)).toBeCloseTo(1.8);
|
||||
expect(engineOffStarScale(1, 0.5)).toBeCloseTo(1.4);
|
||||
expect(engineOffStarScale(1, 1)).toBe(1);
|
||||
// Combined with the daybreak blend, the stars are gone by full dawn.
|
||||
const palette = createMutableChapterPalette('destination');
|
||||
blendDaybreak(palette, 1);
|
||||
expect(palette.starVisibility * engineOffStarScale(1, 1)).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user