fix(ui): scope hand-tracking activation + clean MissionNotification video branch
- HandTrackingProvider: drop the physics-mode auto-activation that turned the camera/MediaPipe pipeline on whenever any interactable was nearby (e.g. walking near the ebike to mount it). Hand tracking is now gated *only* by the active repair-mission step (inspected, repairing, reassembling, done). When testing in TestMap, set mainState=ebike + currentStep=inspected via the GameStateDebugPanel. - MissionNotification: video branch no longer inherits the CRT-style enter/scan/flicker/sepia animations applied to the PNG branch via index.css. The webm assets already animate themselves, so the wrapping container is rendered with inline styles only (clip-path silhouette preserved, but no .__image-wrap::before scan line, no .__image flicker filter, no parent enter animation, no drop-shadow).
This commit is contained in:
@@ -6,6 +6,12 @@ import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
|||||||
// <video> element renders at the wrong dimensions and shifts the layout.
|
// <video> element renders at the wrong dimensions and shifts the layout.
|
||||||
const NOTIFICATION_ASPECT_RATIO = "589 / 211";
|
const NOTIFICATION_ASPECT_RATIO = "589 / 211";
|
||||||
|
|
||||||
|
// Same clip-path as `.mission-notification__image-wrap` in index.css. Inlined
|
||||||
|
// here so the video branch can re-use the silhouette without inheriting the
|
||||||
|
// scan-line `::before` and CRT animations applied to the PNG branch.
|
||||||
|
const NOTIFICATION_CLIP_PATH =
|
||||||
|
"polygon(0 0, 100% 0, 100% 69%, 88% 100%, 0 100%)";
|
||||||
|
|
||||||
interface MissionNotificationProps {
|
interface MissionNotificationProps {
|
||||||
mission?: RepairMissionId;
|
mission?: RepairMissionId;
|
||||||
imagePath?: string;
|
imagePath?: string;
|
||||||
@@ -24,14 +30,34 @@ export function MissionNotification({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`mission-notification${visible ? "" : " mission-notification--hidden"}`}
|
className={`mission-notification${visible ? "" : " mission-notification--hidden"}`}
|
||||||
|
// Webm assets already animate themselves; suppress the CRT entrance
|
||||||
|
// flicker + drop-shadow that index.css applies to all .mission-notification
|
||||||
|
// nodes so the video plays in a clean container.
|
||||||
|
style={
|
||||||
|
isVideo
|
||||||
|
? {
|
||||||
|
animation: "none",
|
||||||
|
filter: "none",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
<div className="mission-notification__glow" />
|
{isVideo ? null : <div className="mission-notification__glow" />}
|
||||||
<span className="mission-notification__image-wrap">
|
{isVideo ? (
|
||||||
{isVideo ? (
|
<span
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
display: "block",
|
||||||
|
overflow: "hidden",
|
||||||
|
clipPath: NOTIFICATION_CLIP_PATH,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
className="mission-notification__image"
|
|
||||||
style={{
|
style={{
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
height: "auto",
|
||||||
aspectRatio: NOTIFICATION_ASPECT_RATIO,
|
aspectRatio: NOTIFICATION_ASPECT_RATIO,
|
||||||
objectFit: "cover",
|
objectFit: "cover",
|
||||||
}}
|
}}
|
||||||
@@ -43,14 +69,16 @@ export function MissionNotification({
|
|||||||
playsInline
|
playsInline
|
||||||
preload="auto"
|
preload="auto"
|
||||||
/>
|
/>
|
||||||
) : (
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="mission-notification__image-wrap">
|
||||||
<img
|
<img
|
||||||
className="mission-notification__image"
|
className="mission-notification__image"
|
||||||
src={src}
|
src={src}
|
||||||
alt="Nouvel objectif de mission"
|
alt="Nouvel objectif de mission"
|
||||||
/>
|
/>
|
||||||
)}
|
</span>
|
||||||
</span>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { HAND_TRACKING_LINGER_MS } from "@/data/handTrackingConfig";
|
import { HAND_TRACKING_LINGER_MS } from "@/data/handTrackingConfig";
|
||||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
|
||||||
import { useDebugStore } from "@/hooks/debug/useDebugStore";
|
import { useDebugStore } from "@/hooks/debug/useDebugStore";
|
||||||
import { useInteraction } from "@/hooks/interaction/useInteraction";
|
|
||||||
import {
|
import {
|
||||||
HAND_TRACKING_IDLE_SNAPSHOT,
|
HAND_TRACKING_IDLE_SNAPSHOT,
|
||||||
HandTrackingContext,
|
HandTrackingContext,
|
||||||
@@ -25,8 +23,14 @@ export function HandTrackingProvider({
|
|||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}): React.JSX.Element {
|
}): React.JSX.Element {
|
||||||
const sceneMode = useSceneMode();
|
// Hand tracking is gated *only* by the active repair-mission step. We
|
||||||
const repairNeedsHands = useGameStore((state) => {
|
// intentionally do NOT activate it from generic interactable proximity
|
||||||
|
// (e.g. standing next to the ebike to mount it) — that previously caused
|
||||||
|
// hand tracking to spin up around any interactable in the physics
|
||||||
|
// (TestMap) scene mode, even though the player wasn't in a step that
|
||||||
|
// actually uses hands. Use the GameStateDebugPanel to set
|
||||||
|
// mainState=ebike + currentStep=inspected when testing in TestMap.
|
||||||
|
const requested = useGameStore((state) => {
|
||||||
switch (state.mainState) {
|
switch (state.mainState) {
|
||||||
case "ebike":
|
case "ebike":
|
||||||
return REPAIR_HAND_TRACKING_STEPS.has(state.ebike.currentStep);
|
return REPAIR_HAND_TRACKING_STEPS.has(state.ebike.currentStep);
|
||||||
@@ -39,10 +43,6 @@ export function HandTrackingProvider({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const { nearby, holding, handHolding } = useInteraction();
|
|
||||||
const requested =
|
|
||||||
repairNeedsHands ||
|
|
||||||
(sceneMode === "physics" && (nearby || holding || handHolding));
|
|
||||||
|
|
||||||
// Keep the runtime active a little after `requested` turns off so
|
// Keep the runtime active a little after `requested` turns off so
|
||||||
// MediaPipe has time to initialize the webcam + model + first frame
|
// MediaPipe has time to initialize the webcam + model + first frame
|
||||||
|
|||||||
Reference in New Issue
Block a user