- 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).
- OutroVideoOverlay: stagger reveal so 'Next step :' appears immediately and
'La ferme' fades in 500ms later, instead of both showing at once.
- MissionNotification: enforce 589/211 aspect-ratio + objectFit cover on the
<video> branch so webm assets (square 2000x2000) render at the same place
as the legacy PNG notifications instead of shifting the layout.
- HandTrackingTutorial: add a 5000ms fallback timeout that auto-dismisses
the overlay if MediaPipe never reports a hand (camera blocked, mouse-only
player), so the screen never stays stuck.
Mount two first-time tutorial overlays driven by the game state machine:
- MovementTutorial: visible during the intro reveal and the free-walk
step before the ebike mount, dismissed on the first Z/Q/S/D keydown.
- HandTrackingTutorial: visible during the early ebike repair steps
(fragmented, scanning, inspected), dismissed when MediaPipe detects
any hand on screen.
Both share a generic TutorialOverlay shell (transparent panel, dark
blue border, lucide-react Hand / inline ZQSD keycap icons, centered
text). The overlay sits at z-index 14, behind Subtitles (15) and
the talkie overlay (16), so dialogue/subtitle UI stays in front.
Dismissals stay persistent for the session: keyboard-triggered uses
event-handler setState; hand-detection uses a guarded effect-driven
setState (same pattern as PylonDownedPylon resync).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the live hand visualizer as a light-blue silhouette with a
crisp dark-blue outline, suitable as the primary hand UI (replacing
the buggy 3D glove for the default flow):
- Palm polygon (landmarks 0,1,5,9,13,17) and five finger tubes merged
via an SVG feMorphology filter, so the outline is a single
continuous ring with no internal seams.
- Q curves bow out to two synthetic wrist corners (perpendicular to
the palm centerline) for a rounded heel of palm.
- Straight L edges between MCPs along the top - the filter dilation
rounds the corners visually, no creux.
- Each finger path starts half a stroke inside the palm so the round
base cap is hidden under the palm fill.
- Whole silhouette shrunk to 65% of the tracked hand size around the
centroid, with 0.8 group opacity, and a faint MediaPipe skeleton
overlay (lines + dots) on top.
Update the static fallback silhouettes (HandTrackingFallback) to a
matching curved-path look in a 100x120 viewBox.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Smaller boxes (36x36 key + 36px-tall label) instead of the previous
oversized white pills.
- Dark translucent background (rgba(10, 12, 20, 0.55)) with a 1px
white outline (rgba(255, 255, 255, 0.7)), no border-radius and
white text so the prompt blends with the dark UI instead of being a
bright blob over the 3D scene.
- Key cube now has a 3D keyboard-key effect (inset top highlight +
inset bottom darkening + small bottom drop) so it reads as a
physical key.
- Key and label are visually separated (gap: 8px) but share the same
height for alignment.
- InteractPrompt no longer renders the label box when focused.label is
empty/whitespace, so callers can show the key prompt alone.
Drops the useRepairMovementLocked hook, the RepairMovementLockIndicator
overlay, and all PlayerController gating tied to repair sub-states.
The repair flow no longer freezes player movement or shows a lock
banner; the player keeps full control while interacting with the case.
Pylon-only mission steps (approaching/arrived/npc-return/narrator-outro)
no longer appear in the GameStateDebugPanel sub-state dropdown for the
ebike or farm missions, which use the shorter
locked/waiting/inspected/fragmented/scanning/repairing/reassembling/done
flow.
The talkie folder now ships a single binary GLB; update the four call
sites (TalkieModel, gallery, two repair mission entries) to load
model.glb. The talkie-LOD path is unchanged.
Restore ultra to its original behaviour (50m chunk streaming, HD within
20m, no fog) and introduce a new max preset that disables chunk streaming
entirely (loads all chunks unconditionally) and pushes the HD/LOD swap
distance to 50m. Add chunkStreamingEnabled flag to GraphicsPresetConfig
so the streaming gate honours the preset. The settings card label shows
'All' when streaming is off.
The previous talkie-overlay refactor lost the rest/active behaviour and
left the radio-shake CSS animation running constantly. Restore the
intended polish:
- Position lerp between TALKIE_REST_Y (idle) and TALKIE_ACTIVE_Y
(raised when narrator is speaking) with a subtle floating bob.
- Subtle z-axis float at idle, faster shake when active.
- Gate the CSS radio-shake on the new --active modifier so the talkie
is calm when no narrator dialogue is playing.
- Keep the face-camera rotation [0.18, PI, -0.08] from the original
overlay version.
Visibility still kicks in at the reveal step (no regression on the
recent fix).
Regression introduced by the narrator-video revert (1ad0c4d): the talkie
overlay was hidden whenever no narrator subtitle was active. Restore the
prior behaviour where the talkie stays visible from the reveal step
onward and only the --raised modifier and signal lines depend on the
active narrator dialogue.
- index.css: add visible :focus-visible rings for .site-card-button
and .site-button so keyboard users can see where focus lives
- SiteCard: drop outline:none, add aria-pressed and aria-label so
screen readers announce selection state
- SiteButton: add the .site-button class for the shared focus ring
- SiteDisclaimerScreen: keyboard skip via Enter / Space / Escape, a
role="region" + aria-label wrapper and aria-live="polite" on the
message; honour prefers-reduced-motion on the fade
- IntroVideoPlayer: role="region" with a skip hint in aria-label,
preload="auto", and aria-hidden on the decorative caption span
- loadDialogueManifest: cache the resolved manifest at module level and
dedupe concurrent fetches so each screen no longer re-downloads it
- useGameStore: completeIntroState now also advances intro.currentStep
to "playing" so callers do not need a separate setIntroStep call
- SiteNamingScreen and SiteTransitionOverlay: replace ref-based guards
with an isCancelled flag captured per effect. The previous guards
persisted across StrictMode remounts, leaving mount 2 unable to
re-run the effect after mount 1's chain was cancelled, which broke
the fade animations, the second narrator dialogue and the redirect.
Both screens now also call stopCurrentDialogue on unmount so audio
cannot bleed across routes, and the transition gets a safety timeout
in case the dialogue audio fails to fire its "ended" event
- SiteTransitionOverlay: keep the <Subtitles /> mount inside the
overlay so it renders inside the z-index 1000 stacking context
(above the black screen); the one in SiteLayout sits behind it
- IntroDialogueOverlay: route through playDialogueById instead of
AudioManager.playSoundWithCallback so the narrator subtitles play
in sync, and add the same isCancelled cleanup pattern
- IntroRevealOverlay: rely on completeIntro alone now that it advances
intro.currentStep, and skip the fade when reduced motion is requested
- SiteMobileBlocker: correct logo path from public/... to /...