polish: scene nits — live-lens yard, square stacks, deck-clear billboard, two-layer lamps, flare reach, phone traffic and exposure

This commit is contained in:
2026-10-12 18:59:33 +03:00
parent 71465fa481
commit 3602eeb3d1
23 changed files with 671 additions and 73 deletions

View File

@@ -2,7 +2,15 @@ import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { SceneResourceBag } from '@/scene/resources';
import { BEAM_INTENSITY, createRelayMasts, RUNG_FLASH_GAP_SECONDS, relayLayout } from '@/scene/world/relay-masts';
import {
BEAM_INTENSITY,
createRelayMasts,
RUNG_CORE,
RUNG_FLASH_GAP_SECONDS,
RUNG_HALO,
RUNG_HALO_GAIN,
relayLayout,
} from '@/scene/world/relay-masts';
import { publishRelay } from '@/journey/relay-progress';
import { readSwitchboard } from '@/audio/music-pulse';
import { playRelayTone } from '@/audio/relay-tone';
@@ -54,6 +62,35 @@ describe('relay mast layout', () => {
}
});
it('portrait: one large transmitter carries the rungs of every group, bottom-up in group order', () => {
const masts = relayLayout(0.5, counts, false, 390 / 844);
expect(masts).toHaveLength(4);
expect(masts.filter((mast) => mast.shown)).toHaveLength(1);
const [stand] = masts;
// One stand, and it is big: well over twice the design mast.
masts.forEach((mast) => expect([mast.x, mast.z, mast.scale]).toEqual([stand!.x, stand!.z, stand!.scale]));
expect(stand!.height).toBeGreaterThan(2 * relayLayout(0.5, counts)[0]!.height);
// On screen, left of the road and inside the frame (portrait lens: no shift).
const tanHalfWidth = Math.tan((62 / 2) * (Math.PI / 180)) * (390 / 844);
const fx = stand!.x / (tanHalfWidth * (25 - stand!.z));
expect(fx).toBeLessThan(-0.2);
expect(fx).toBeGreaterThan(-0.7);
// Rungs: one per technology, strictly rising through the groups, inside the mast.
const ys = masts.flatMap((mast) => mast.rungs.map((rung) => rung.y));
expect(ys).toHaveLength(counts.reduce((sum, count) => sum + count, 0));
ys.slice(1).forEach((y, i) => expect(y).toBeGreaterThan(ys[i]!));
expect(ys.at(-1)!).toBeLessThan(stand!.height);
});
it('keeps lit rung cores under the tone shoulder and bloom threshold, violet not white', () => {
const luminance = (c: readonly number[]) => 0.2126 * c[0]! + 0.7152 * c[1]! + 0.0722 * c[2]!;
expect(luminance(RUNG_CORE)).toBeLessThan(0.3);
expect(luminance(RUNG_CORE.map((v, i) => v + RUNG_HALO[i]! * RUNG_HALO_GAIN))).toBeLessThan(0.85);
// Saturated: blue dominates, green is a small fraction (lavender-white would be near-equal channels).
expect(RUNG_CORE[1] / RUNG_CORE[2]).toBeLessThan(0.2);
expect(RUNG_HALO[1] / RUNG_HALO[2]).toBeLessThan(0.2);
});
it('is absent outside the chapter and fades at its edges', () => {
expect(relayLayout(null, counts).every((mast) => mast.presence === 0)).toBe(true);
expect(relayLayout(0, counts).every((mast) => mast.presence === 0)).toBe(true);