# Performance optimization report This report documents the production performance strategy for Mechanica and the hardening work applied for final deployment. ## Targets | Scenario | Target | | --- | ---: | | Desktop viewer frame rate | 60 FPS during normal orbit and animation | | Mobile viewer frame rate | 30 FPS minimum | | Catalogue interaction latency | < 100 ms for search/filter/sort | | Initial route JS budget | < 350 KB gzip excluding vendor chunks | | Largest single machine chunk | < 650 KB gzip excluding GLB/texture assets | | Machine metadata load | < 50 ms parse on typical desktop | | Viewer input response | < 50 ms from pointer movement to camera response | | Share URL parse/apply | < 16 ms for typical state payload | ## Implemented production configuration The Vite configuration includes: - React plugin for production JSX transform. - Path alias `@` for stable imports. - Manual vendor chunking: - `vendor-react` - `vendor-three` - `vendor-animation` - `vendor-state` - `vendor-ui` - `vendor` - CSS code splitting. - Deterministic hashed asset paths. - Asset subfolders for models, environments, fonts, images, and scripts. - Bundle visualizer mode through `npm run analyze`. - Production source maps disabled unless `VITE_ENABLE_SOURCE_MAPS=true`. Vercel configuration includes: - Immutable cache headers for hashed assets. - Long-lived decoder/transcoder cache headers. - SPA rewrites to avoid route-level 404s. - CSP that permits WebAssembly and blob workers needed by compressed asset loaders. ## Runtime optimization policy ### Catalogue - Keep machine registry metadata lightweight. - Store searchable keywords as normalized arrays. - Memoize search/filter/sort derivations. - Avoid loading 3D scene modules on the catalogue route. - Use skeleton cards with fixed dimensions to prevent layout shift. ### Viewer - Lazy-load each machine scene. - Use Suspense loading fallbacks with stable panel dimensions. - Clamp device pixel ratio: - desktop: up to `2` - tablet: up to `1.5` - mobile: up to `1.25` - Prefer one environment light plus a small number of shadow-casting lights. - Disable expensive shadows on low-power/mobile profiles when necessary. - Use instancing for repeated geometry. - Use shared materials for procedural parts. - Keep per-frame allocations out of animation updates. - Do not write to Zustand stores every frame. - Dispose runtime clones when leaving a scene. ### Animation - Store animation control state in Zustand, not animated transforms. - Use render-loop updates for transforms. - Keep cycle phase derived from elapsed time, RPM, and time scale. - Clamp RPM and time scale before animation modules receive them. - Respect reduced-motion by defaulting to paused or lower speed while preserving user control. ### Cross-section and transparency - Reuse clipping planes instead of creating new planes per render. - Mark only affected materials as requiring clipping. - Avoid sorting-heavy transparent stacks where a simple x-ray material is sufficient. - Provide opacity sliders per part, but debounce expensive material updates. ### Labels and annotations - Hide labels below small viewport thresholds unless explicitly enabled. - Use occlusion-aware labels where available. - Avoid rendering hundreds of DOM labels; group minor parts under expandable annotations. ## Profiling workflow Run a production build locally: ```bash npm run build npm run preview ``` Open Chrome DevTools: 1. Performance panel: record orbiting, play/pause, and exploded view changes. 2. Memory panel: switch between machines and confirm old geometries/materials are released. 3. Rendering panel: inspect FPS, paint flashing, and layout shift. 4. Network panel: confirm JS chunks and GLB assets use expected cache headers in deployed builds. Generate bundle report: ```bash npm run analyze ``` Inspect `dist/bundle-report.html` for: - Three.js leaking into initial catalogue chunk. - Machine modules bundled into the home route. - Duplicate animation libraries. - Unexpected large SVG or image imports. - GLB files accidentally imported into the primary JS bundle. ## Mobile tuning checklist - Test widths: 430, 390, 375. - Test tablet widths: 1024 and 768. - Use Chrome CPU throttling at 4x for low-end approximation. - Confirm side panels collapse into drawers. - Confirm controls have at least 44px touch targets. - Confirm canvas does not scroll the page during orbit gestures. - Confirm bottom status bar does not cover primary actions. - Confirm annotations do not overwhelm the viewport. - Confirm reduced DPR does not make labels illegible. ## Measurement log template Use this table in release notes after measuring a candidate build: | Build | Route/machine | Device | FPS orbit | FPS animation | JS transfer gzip | GLB transfer | Notes | | --- | --- | --- | ---: | ---: | ---: | ---: | --- | | preview URL | four-stroke-petrol-engine | Desktop Chrome | | | | | | | preview URL | planetary-gearbox | Desktop Chrome | | | | | | | preview URL | jet-engine-turbojet | Pixel 5 emulation | | | | | | | preview URL | ball-bearing | iPad landscape | | | | | | ## Release gates A production release should not be promoted if any of these are true: - Initial catalogue route loads all machine scene modules. - A common desktop interaction drops below 45 FPS for more than one second. - A mobile interaction drops below 24 FPS for more than one second. - Switching machines steadily increases memory after garbage collection. - A single non-machine JS chunk exceeds 900 KB gzip. - Share links fail to reconstruct the active machine. - Reduced-motion mode still auto-plays large camera animations without user initiation.