feat: daybreak finale turns the contact arrival into a sunrise with a dawn chord

This commit is contained in:
2026-10-11 05:32:08 +03:00
parent c9e6ac0f29
commit b6e22a1299
37 changed files with 756 additions and 44 deletions

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { SceneResourceBag } from '@/scene/resources';
import { createSun } from '@/scene/world/sun';
import { createSun, DAWN_LIFT } from '@/scene/world/sun';
import { createMutableChapterPalette } from '@/scene/world/palette';
interface Halo {
@@ -107,6 +107,39 @@ describe('destination sun', () => {
resources.dispose();
});
it('lifts the disc, widens the halo and fades the meteors as the dawn rises, without allocating', () => {
const resources = new SceneResourceBag();
const sun = createSun(resources);
const palette = createMutableChapterPalette('destination');
const disc = sun.group.getObjectByName('horizon-sun') as unknown as {
position: { y: number };
material: { uniforms: Record<string, { value: number }> };
};
const limb = sun.group.getObjectByName('sun-limb-glow') as unknown as { position: { y: number } };
const halo = sun.group.getObjectByName('horizon-halo') as unknown as Halo & { position: { y: number } };
const meteors = sun.group.getObjectByName('arrival-meteors') as unknown as {
material: { uniforms: { uFade: { value: number } } };
};
sun.update(palette, 1, 10, 0, true, 0);
sun.update(palette, 1, 20, 0, true, 0);
const night = { y: disc.position.y, width: halo.scale.x };
expect(meteors.material.uniforms.uFade.value).toBe(1);
const allocation = resources.snapshot();
sun.update(palette, 1, 20, 0, true, 0.7);
expect(meteors.material.uniforms.uFade.value).toBe(1);
sun.update(palette, 1, 20, 0, true, 1);
expect(disc.position.y).toBeCloseTo(night.y + DAWN_LIFT);
expect(DAWN_LIFT).toBe(58);
expect(limb.position.y).toBe(disc.position.y);
expect(halo.position.y).toBe(disc.position.y);
expect(halo.scale.x / night.width).toBeCloseTo(1.18);
expect(disc.material.uniforms.uDawn!.value).toBe(1);
expect(meteors.material.uniforms.uFade.value).toBe(0);
expect(resources.snapshot()).toEqual(allocation);
resources.dispose();
});
it('does not pulse from route progress alone and cancels when leaving Contact', () => {
const resources = new SceneResourceBag();
const sun = createSun(resources);