feat: scroll-driven maintenance window set-piece in the infrastructure chapter

This commit is contained in:
2026-10-11 04:25:28 +03:00
parent bd9bd692b9
commit 52b4a1ad14
45 changed files with 1063 additions and 10 deletions

View File

@@ -26,9 +26,13 @@ import { createCoachworkEnvironment } from '@/scene/world/coachwork-light';
import { createTravelDistance } from '@/scene/world/travel-distance';
import {
createMutableChapterPalette,
STORM_STAR_SCALE,
STORM_TINT,
writeChapterPalette,
type MutableChapterPalette,
} from '@/scene/world/palette';
import { createMaintenanceWindow } from '@/scene/world/maintenance-window';
import { maintenanceProgress } from '@/journey/maintenance-progress';
import { isTestFault, recordTestFaultEvent } from '@/testing/fault-hooks';
declare const __PORTFOLIO_TEST_BUILD__: boolean;
@@ -141,7 +145,16 @@ export async function createProceduralSceneGeneration(
maximumTextureSize: renderer.capabilities.maxTextureSize,
});
const atmosphere = createAtmosphere(context.resources, quality);
// The 03:12 window. Phones, coarse pointers and the low tier keep only the
// power-down, the re-light and the palette nudge: no rain, packets or lightning.
const maintenance = createMaintenanceWindow(context.resources);
const maintenanceTier =
context.quality === 'low'
|| (typeof matchMedia === 'function' && matchMedia('(pointer: coarse)').matches)
? 'lite'
: 'full';
scene.add(
maintenance.group,
atmosphere.group,
road.group,
expressway.group,
@@ -307,6 +320,19 @@ export async function createProceduralSceneGeneration(
terrainSpectrumPhase =
(terrainSpectrumPhase + deltaSeconds * overdrive * 0.38) % 1;
writeChapterPalette(targetPalette, snapshot.chapter, snapshot.chapter, 0);
// The 03:12 window: section-local scroll progress drives every beat.
const service = maintenance.update(
maintenanceProgress(snapshot),
snapshot.direction,
deltaSeconds,
maintenanceTier,
);
if (service.storm > 0) {
targetPalette.sky.lerp(STORM_SKY, 0.55 * service.storm);
targetPalette.fog.lerp(STORM_SKY, 0.4 * service.storm);
targetPalette.starVisibility *= 1 - (1 - STORM_STAR_SCALE) * service.storm;
}
districts.setServiceState(service.dim, service.relight);
// Engine off: parked at the destination the sky drops about 10%, more
// stars come out, and the drive winds down to a stop.
const engineOff = THREE.MathUtils.smoothstep(outroProgress, 0.12, 0.7);
@@ -388,6 +414,13 @@ export async function createProceduralSceneGeneration(
);
sign.setResting(snapshot.activeSectionId);
sign.update(snapshot.sign, now, snapshot.driveSpeed);
// Distant lightning lifts the sky a little, toward a still-dark lilac.
if (service.flash > 0) {
flashRestore.sky.copy(currentPalette.sky);
flashRestore.fog.copy(currentPalette.fog);
currentPalette.sky.lerp(LIGHTNING_SKY, service.flash * 0.7);
currentPalette.fog.lerp(LIGHTNING_SKY, service.flash * 0.3);
}
atmosphere.update(
driveDistance,
visualSpeed,
@@ -402,6 +435,10 @@ export async function createProceduralSceneGeneration(
.copy(currentPalette.haze)
.lerp(currentPalette.terrainHigh, 0.26);
ambient.groundColor.copy(currentPalette.fog);
if (service.flash > 0) {
currentPalette.sky.copy(flashRestore.sky);
currentPalette.fog.copy(flashRestore.fog);
}
ambient.intensity = 1.05 + currentPalette.warmth * 0.18;
carKeyLight.color.setHex(0xb2d5ff);
carKeyLight.intensity = 32 + response.bloomEnergy * 3.6 + overdrive * 8;
@@ -620,6 +657,8 @@ export async function createProceduralSceneGeneration(
time: elapsedSeconds,
letterbox: 0.115 * (1 - THREE.MathUtils.smoothstep(introProgress, 0.5, 1)),
energy: overdrive,
rain: service.rain,
rainTime: elapsedSeconds,
});
postProcessor.render(scene, camera);
} else {
@@ -648,6 +687,9 @@ const OUTRO_SECONDS = 5.5;
const OUTRO_RETURN_SECONDS = 1.6;
const OUTRO_REACH = 0.62;
const ENGINE_OFF_NIGHT = new THREE.Color(0x000000);
const STORM_SKY = new THREE.Color(STORM_TINT);
const LIGHTNING_SKY = new THREE.Color(0x5a5ea8);
const flashRestore = { sky: new THREE.Color(), fog: new THREE.Color() };
/** Plates rise back over 720 ms with up to three 90 ms staggers. */
const INTERLUDE_RISE_MS = 800;