Three fixes for the ebike-breakdown substep:
1. PlayerController: the previous `if (!isEbikeBreakdown)` guard
zeroed _wishDir for everyone during breakdown, including the
player after they had been auto-dismounted to walk mode. Narrow
the guard to `isEbikeMounted && isEbikeBreakdown` so the bike
stops accepting drive input but the player on foot can move.
2. Ebike: track `window.ebikeBreakdownActive` in component state
and hide the InteractableObject (and therefore the interact
prompt UI) while the breakdown sequence is active. The bike must
read as inert and non-interactive while the panne dialogue plays
and during the auto-dismount that follows.
3. ebikeConfig: bump EBIKE_INTRO_BREAKDOWN_DISTANCE from 15 m to
450 m so the panne triggers after a real ride instead of a few
meters from the parked spawn.
The new react-compiler-aware lint rules flag legitimate Three.js
external-system synchronizations (texture/uniform/AnimationAction
mutations) and a derived-state reset in PylonDownedPylon. None of
these are bugs — they're the canonical way to bridge React state
with imperative graphics objects — so they're annotated with
targeted eslint-disable comments and a small reorder.
- EbikeGPSMap: disable on uniform/texture sync effects
- EbikeSpeedmeter: disable around the canvas+texture useFrame sync
- PylonFarmerNPC: disable around playAnim (drei AnimationAction
fadeIn/fadeOut/setLoop/clampWhenFinished) and the effects/frame
callbacks that invoke it
- PylonDownedPylon: move showUpright/isPylonInteractive declarations
above the useFrame that reads them (fixes access-before-declared)
and disable set-state-in-effect on the per-step isRaised reset
Add lockInput option (default true) to animateCameraTransformTransition
so ebike mount/dismount can keep player input active during the 1s
camera tween instead of locking via setCinematicPlaying.
Also drop the unused camPointPos/dropPointPos debug vars and the
matching debugRestingPosition state — the consuming JSX has been
commented out for a while.
While the player is mounted on the e-bike and pressing a movement key,
the persistent 'Descendre du bike' prompt was visible on screen and
polluted the view during gameplay. The InteractableObject is now
unmounted as soon as window.ebikeDriveInputActive flips to true and
remounted the moment the bike comes to a stop.
The driving signal is read in a useFrame and only flips React state
on transitions, so this adds zero per-frame re-renders.
Adds an opt-out 'snapToTerrain' prop on Ebike so the parked position
keeps the explicit Y supplied by callers instead of resolving against
the world terrain GLTF. TestMap passes snapToTerrain={false} since it
does not render the world terrain — without this the bike was being
positioned at the invisible terrain height, far above the test floor,
and looked missing.
- Extract SHADOW_CONFIG into lightingConfig.ts (bias=0, normalBias=0,
cameraSize=95) matching the historically working values from develop.
- Drop SceneShadowWarmup; rely on sun.shadow.autoUpdate=true for
steady-state refresh.
- Enable cloud castShadow and traverse Ebike meshes for cast/receive.
- Fix all 63 ESLint errors across codebase
- Consolidate MaterialWithTextureSlots type in src/types/three/three.ts
- Add CSS custom properties for design tokens
- Extract ebike constants to src/data/ebike/ebikeConfig.ts
- Add proper TypeScript types for window extensions
- Fix React hooks violations (refs during render, setState in effects)
- Remove unused exports and redundant CSS
- Add type guards for Three.js material handling
- Clean up AI slop comments and legacy CSS patterns