fix(types): satisfy strict tsc for production build (deploy unblock)
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (push) Has been cancelled
📊 Quality / 🔒 Security Audit (push) Has been cancelled
📊 Quality / 📋 Dependency Freshness (push) Has been cancelled
📊 Quality / 📦 Bundle Size (push) Has been cancelled
🔍 Lint / 🏗 Build (push) Has been cancelled
🔍 Lint / 🪄 Check lint (push) Has been cancelled
🔍 Lint / 🎨 Check format (push) Has been cancelled
🔍 Lint / 🔎 Typecheck (push) Has been cancelled
📊 Quality / 🔒 Security Audit (push) Has been cancelled
📊 Quality / 📋 Dependency Freshness (push) Has been cancelled
📊 Quality / 📦 Bundle Size (push) Has been cancelled
🔍 Lint / 🏗 Build (push) Has been cancelled
This commit is contained in:
@@ -181,9 +181,12 @@ export const EbikeGPSMap: React.FC<EbikeGPSMapProps> = ({
|
||||
|
||||
// Sync texture into uniform when it changes (canvas resize)
|
||||
useEffect(() => {
|
||||
const mapUniform = shaderMat.uniforms.map;
|
||||
if (!mapUniform) return;
|
||||
|
||||
// External Three.js material uniform sync — intentional side effect.
|
||||
// eslint-disable-next-line react-hooks/immutability
|
||||
shaderMat.uniforms.map.value = texture;
|
||||
mapUniform.value = texture;
|
||||
}, [shaderMat, texture]);
|
||||
|
||||
// Cleanup on unmount
|
||||
|
||||
@@ -146,16 +146,6 @@ export function EbikeIntroSequence(): React.JSX.Element | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mainState == "pylon") {
|
||||
if (pylonStep === "approaching") {
|
||||
return <MissionNotification mission="pylon" visible />;
|
||||
}
|
||||
if (pylonStep === "narrator-outro") {
|
||||
return <MissionNotification mission="farm" visible />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
introStep !== "reveal" &&
|
||||
introStep !== "await-ebike-mount" &&
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import { useSubtitleStore } from "@/managers/stores/useSubtitleStore";
|
||||
import { AudioManager } from "@/managers/AudioManager";
|
||||
|
||||
const HISTOIRE_AUDIO_PATH = "/sounds/dialogue/narrateur_histoireelectricienne.mp3";
|
||||
const HISTOIRE_AUDIO_PATH =
|
||||
"/sounds/dialogue/narrateur_histoireelectricienne.mp3";
|
||||
const OUTRO_DELAY_MS = 5_000; // delay after audio ends before transitioning to outro
|
||||
|
||||
/**
|
||||
@@ -78,9 +79,12 @@ function useHistoireSubtitlePlayback(
|
||||
({ start, end }) => t >= start && t < end,
|
||||
);
|
||||
if (idx >= 0) {
|
||||
const text = HISTOIRE_BLOCKS[idx];
|
||||
if (text === undefined) return;
|
||||
|
||||
setActiveSubtitle({
|
||||
speaker: "Narrateur",
|
||||
text: HISTOIRE_BLOCKS[idx],
|
||||
text,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -136,7 +140,9 @@ export function FarmNarrativeFlow(): null {
|
||||
|
||||
// After the audio finishes, wait 5 s then transition to outro.
|
||||
// The timeout ID is kept in a ref so we can cancel on unmount.
|
||||
const outroTimeoutRef = useRef<ReturnType<typeof window.setTimeout> | null>(null);
|
||||
const outroTimeoutRef = useRef<ReturnType<typeof window.setTimeout> | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
@@ -33,7 +33,9 @@ export function PylonDownedPylon(): React.JSX.Element | null {
|
||||
);
|
||||
// Snap to terrain so the downed/upright model sits flush on the ground,
|
||||
// matching the Y adjustment that InstancedMapAsset applies to the same node.
|
||||
const position = useTerrainSnappedPosition(pylonAnchor ?? PYLON_WORLD_POSITION);
|
||||
const position = useTerrainSnappedPosition(
|
||||
pylonAnchor ?? PYLON_WORLD_POSITION,
|
||||
);
|
||||
const [isStraightening, setIsStraightening] = useState(false);
|
||||
// Keeps the pylon upright after the animation completes while
|
||||
// PylonFarmerNPC plays the post-raise audio sequence.
|
||||
@@ -63,7 +65,9 @@ export function PylonDownedPylon(): React.JSX.Element | null {
|
||||
if (!group) return;
|
||||
|
||||
if (!isStraightening || straightenStartRef.current === null) {
|
||||
group.rotation.set(...(isRaised ? PYLON_UPRIGHT_ROTATION : PYLON_DOWNED_ROTATION));
|
||||
group.rotation.set(
|
||||
...(isRaised ? PYLON_UPRIGHT_ROTATION : PYLON_DOWNED_ROTATION),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,11 +108,7 @@ export function PylonDownedPylon(): React.JSX.Element | null {
|
||||
if (!shouldRender) return null;
|
||||
|
||||
return (
|
||||
<group
|
||||
ref={groupRef}
|
||||
position={position}
|
||||
rotation={PYLON_DOWNED_ROTATION}
|
||||
>
|
||||
<group ref={groupRef} position={position} rotation={PYLON_DOWNED_ROTATION}>
|
||||
<primitive object={scene.clone(true)} />
|
||||
{isPylonInteractive ? (
|
||||
<InteractableObject
|
||||
|
||||
@@ -159,7 +159,9 @@ function PylonFarmerNPCContent(): React.JSX.Element {
|
||||
} else if (step === "done") {
|
||||
// NPC reappears at repair completion — position at the post-raise spot,
|
||||
// facing the pylon, playing idle.
|
||||
currentPosRef.current.set(...PYLON_FARMER_NPC_AFTER_POSITION_pylone_straight);
|
||||
currentPosRef.current.set(
|
||||
...PYLON_FARMER_NPC_AFTER_POSITION_pylone_straight,
|
||||
);
|
||||
savedRotationYRef.current = faceToward(
|
||||
currentPosRef.current,
|
||||
PYLON_WORLD_POSITION,
|
||||
|
||||
@@ -28,11 +28,9 @@ export function PylonNarrativeFlow(): React.JSX.Element | null {
|
||||
|
||||
void (async () => {
|
||||
// 1. Play the generator powerdown sound effect
|
||||
const sfx = AudioManager.getInstance().playSound(
|
||||
PYLON_POWERDOWN_SFX,
|
||||
1,
|
||||
{ category: "sfx" },
|
||||
);
|
||||
const sfx = AudioManager.getInstance().playSound(PYLON_POWERDOWN_SFX, 1, {
|
||||
category: "sfx",
|
||||
});
|
||||
|
||||
// 2. Wait for it to finish (or skip if it can't load)
|
||||
if (sfx) {
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from "@/utils/dialogues/playDialogue";
|
||||
|
||||
const TYPEWRITER_CHAR_DELAY_MS = 150;
|
||||
const TYPEWRITER_START_DELAY_MS = 12000;
|
||||
// Fallback in case nothing else triggers the typewriter (audio failed to
|
||||
// load, no subtitles, "ended" never fires). Long enough not to fire
|
||||
// before the narration on a slow load.
|
||||
|
||||
@@ -16,7 +16,10 @@ export function OutroVideoOverlay(): React.JSX.Element | null {
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
window.addEventListener("outro-cinematic-complete", handleCinematicComplete);
|
||||
window.addEventListener(
|
||||
"outro-cinematic-complete",
|
||||
handleCinematicComplete,
|
||||
);
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
"outro-cinematic-complete",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { isDebugEnabled } from "@/utils/debug/isDebugEnabled";
|
||||
@@ -50,11 +50,10 @@ export function ZoneDetection({
|
||||
zone,
|
||||
onEnter,
|
||||
height,
|
||||
}: ZoneDetectionProps): React.JSX.Element {
|
||||
}: ZoneDetectionProps): React.JSX.Element | null {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const hasTriggeredRef = useRef(false);
|
||||
const onEnterRef = useRef(onEnter);
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
onEnterRef.current = onEnter;
|
||||
@@ -75,9 +74,8 @@ export function ZoneDetection({
|
||||
if (_cameraPos.y > zone.position[1] + zoneHeight / 2) return;
|
||||
|
||||
hasTriggeredRef.current = true;
|
||||
setIsActive(true);
|
||||
onEnterRef.current();
|
||||
});
|
||||
|
||||
return <ZoneDebugVisual zone={zone} active={isActive} />;
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user