# RFCP Rust Migration Plan ## Python/React/Electron → Tauri/SvelteKit/Rust **Version:** 1.0 **Date:** February 2026 **Status:** Planning --- ## Executive Summary This document provides a comprehensive migration plan for RFCP (RF Coverage Planner) from the current Python/React/Electron stack to Tauri/SvelteKit/Rust. The migration targets: - **Performance:** 50km full-preset calculations (currently times out at ~20km) - **Bundle Size:** 5-15MB (currently 170MB+) - **Memory:** Constant memory for any radius (currently OOMs on large areas) --- ## Part 1: Current Architecture Analysis ### 1.1 High-Level Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ Electron Shell │ │ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ │ │ React Frontend │ │ Python Backend (PyInstaller) │ │ │ │ - Vite/TypeScript │ │ - FastAPI/Uvicorn │ │ │ │ - Leaflet/WebGL │◄──►│ - NumPy/SciPy │ │ │ │ - Zustand stores │HTTP│ - Motor (MongoDB) │ │ │ │ - IndexedDB │ WS │ - ProcessPoolExecutor │ │ │ └─────────────────────┘ └─────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ┌─────────────────┼─────────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ SRTM │ │ OSM │ │ MongoDB │ │ Tiles │ │ Overpass │ │ │ └──────────┘ └──────────┘ └──────────┘ ``` ### 1.2 Complete Module Map #### Backend (`backend/app/`) ``` backend/app/ ├── main.py # FastAPI app, CORS, lifespan ├── core/ │ ├── config.py # Pydantic Settings (MONGODB_URL, TERRAIN_DIR) │ ├── database.py # Motor async MongoDB singleton │ ├── calculator.py # Legacy calc engine (unused) │ ├── engine.py # Legacy RF engine (unused) │ ├── grid.py # Legacy grid utils (unused) │ └── result.py # Legacy result structs (unused) ├── models/ │ ├── site.py # Site/Sector Pydantic models │ └── project.py # Project/CoverageSettings models ├── api/ │ ├── deps.py # FastAPI dependencies │ ├── websocket.py # WebSocket progress handler │ └── routes/ │ ├── coverage.py # POST /calculate, /preview, /link-budget (1600 lines) │ ├── terrain.py # GET /elevation, /profile │ ├── health.py # GET /health │ ├── projects.py # Project CRUD │ ├── regions.py # Region management │ ├── system.py # System info │ └── gpu.py # GPU detection ├── geometry/ │ ├── haversine.py # Great-circle distance │ ├── los.py # Line-of-sight with earth curvature │ ├── diffraction.py # ITU-R P.526 knife-edge │ ├── intersection.py # Ray-polygon tests │ └── reflection.py # Specular reflection math ├── propagation/ │ ├── base.py # PropagationModel ABC │ ├── free_space.py # FSPL model (>2GHz) │ ├── okumura_hata.py # 150-1500 MHz │ ├── cost231_hata.py # 1500-2000 MHz (LTE) │ ├── cost231_wi.py # Street canyon model │ ├── itu_r_p1546.py # 30-3000 MHz field strength │ ├── itu_r_p526.py # Knife-edge diffraction │ └── longley_rice.py # VHF (<150 MHz) ├── services/ │ ├── coverage_service.py # MAIN ORCHESTRATOR (1630 lines) │ ├── parallel_coverage_service.py # ProcessPool/Ray parallelism │ ├── terrain_service.py # SRTM loading, mmap, bilinear interp │ ├── buildings_service.py # OSM buildings fetch/cache │ ├── los_service.py # Terrain LOS analysis │ ├── dominant_path_service.py # BOTTLENECK: reflection/diffraction paths │ ├── materials_service.py # Building penetration loss │ ├── street_canyon_service.py # Urban canyon propagation │ ├── reflection_service.py # Single/double bounce │ ├── vegetation_service.py # Forest attenuation │ ├── water_service.py # Water reflection gain │ ├── weather_service.py # Rain attenuation (ITU-R P.838) │ ├── indoor_service.py # Indoor penetration loss │ ├── atmospheric_service.py # O2/H2O absorption │ ├── spatial_index.py # Grid-based building index │ ├── geometry_vectorized.py # NumPy batch geometry ops │ ├── gpu_service.py # CuPy/NumPy abstraction │ ├── gpu_backend.py # GPU array manager │ ├── cache.py # Generic cache utilities │ ├── cache_db.py # SQLite cache backend │ ├── osm_client.py # Overpass API client │ ├── tile_processor.py # Tiled calculation support │ └── boundary_service.py # Coverage contour generation └── utils/ ├── logging.py # Logging config ├── progress.py # Progress reporting └── units.py # Unit conversions ``` #### Frontend (`frontend/src/`) ``` frontend/src/ ├── App.tsx # Main app, layout, state wiring ├── main.tsx # React entry point ├── index.css # Tailwind base ├── components/ │ ├── map/ │ │ ├── Map.tsx # Leaflet MapContainer │ │ ├── SiteMarker.tsx # Site markers with sector wedges │ │ ├── WebGLRadialCoverageLayer.tsx # GPU radial gradients (NEW) │ │ ├── WebGLCoverageLayer.tsx # GPU texture interpolation │ │ ├── GeographicHeatmap.tsx # Canvas fallback │ │ ├── HeatmapTileRenderer.ts # Tile-based canvas renderer │ │ ├── CoverageBoundary.tsx # Contour polygon │ │ ├── ElevationLayer.tsx # Terrain visualization │ │ ├── ElevationDisplay.tsx # Cursor elevation │ │ ├── CoordinateGrid.tsx # Lat/lon grid overlay │ │ ├── MeasurementTool.tsx # Distance measurement │ │ ├── TerrainProfile.tsx # Elevation profile chart │ │ ├── LinkBudgetOverlay.tsx # TX-RX link display │ │ ├── HeatmapLegend.tsx # RSRP color legend │ │ └── MapExtras.tsx # Miscellaneous overlays │ ├── panels/ │ │ ├── SiteList.tsx # Site management (19KB) │ │ ├── BatchEdit.tsx # Batch operations (14KB) │ │ ├── SiteForm.tsx # Site form component │ │ ├── LinkBudgetPanel.tsx # Link budget analysis │ │ ├── CoverageStats.tsx # Calculation statistics │ │ ├── ResultsPanel.tsx # Results summary │ │ ├── HistoryPanel.tsx # Calculation history │ │ ├── ProjectPanel.tsx # Project save/load │ │ ├── FrequencyBandPanel.tsx # Frequency presets │ │ ├── FrequencySelector.tsx # Frequency input │ │ ├── BatchFrequencyChange.tsx # Batch frequency edit │ │ ├── SiteImportExport.tsx # Import/export │ │ └── ExportPanel.tsx # KML/GeoJSON export │ ├── modals/ │ │ ├── SiteConfigModal.tsx # Create/edit site dialog │ │ ├── ModalBackdrop.tsx # Modal backdrop │ │ └── index.ts # Modal exports │ └── ui/ │ ├── Button.tsx # Styled button │ ├── Input.tsx # Text input │ ├── NumberInput.tsx # Numeric input │ ├── Toast.tsx # Toast notifications │ ├── ThemeToggle.tsx # Dark/light mode │ ├── GPUIndicator.tsx # GPU status │ └── ConfirmDialog.tsx # Confirmation modal ├── store/ │ ├── sites.ts # Site CRUD + IndexedDB │ ├── coverage.ts # Coverage calculation state │ ├── settings.ts # UI/map preferences │ ├── history.ts # Undo/redo stack │ ├── projects.ts # Project management │ ├── calcHistory.ts # Calculation history │ └── tools.ts # Active tool state ├── services/ │ ├── api.ts # HTTP API client │ ├── websocket.ts # WebSocket client │ └── terrain.ts # Terrain utilities ├── hooks/ │ ├── useWebSocket.ts # WS connection hook │ ├── useKeyboardShortcuts.ts # Keyboard shortcuts │ ├── useElevation.ts # Elevation queries │ └── useUnsavedChanges.ts # Unsaved changes warning ├── types/ │ ├── site.ts # Site interfaces │ ├── coverage.ts # Coverage interfaces │ └── frequency.ts # Frequency types ├── rf/ │ ├── fspl.ts # Free-space path loss │ ├── antenna-pattern.ts # Antenna gain patterns │ ├── calculator.ts # Client-side RF utils │ └── utils.ts # RF helpers ├── db/ │ └── schema.ts # Dexie IndexedDB schema ├── lib/ │ └── desktop.ts # Electron IPC bridge ├── constants/ │ ├── frequencies.ts # Frequency band presets │ └── rsrp-thresholds.ts # Signal thresholds └── utils/ ├── logger.ts # Logging utility ├── geographicScale.ts # Geographic calculations └── colorGradient.ts # Color utilities ``` #### Desktop (`desktop/`) ``` desktop/ ├── main.js # Electron main process ├── preload.js # IPC bridge ├── splash.html # Loading screen ├── package.json # Electron-builder config └── assets/ ├── icon.ico # Windows icon ├── icon.png # Linux icon └── icon.icns # macOS icon ``` ### 1.3 Data Flow Diagram ``` ┌──────────────────────────────────────────────────────────────────────────────┐ │ Coverage Calculation Pipeline │ └──────────────────────────────────────────────────────────────────────────────┘ User clicks "Calculate" │ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Frontend │ │ WebSocket or │ │ Backend │ │ useCoverageStore│────►│ HTTP POST │────►│ /api/coverage/ │ │ .calculate() │ │ │ │ calculate │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ ┌────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────┐ │ CoverageService.calculate() │ │ │ │ 1. apply_preset() ─────────────────────────┐ │ │ │ │ │ 2. _fetch_osm_grid_aligned() │ │ │ ├── buildings_service.fetch_buildings() │ │ │ ├── street_canyon_service.fetch_streets()│ │ │ ├── water_service.fetch_water_bodies() │ │ │ └── vegetation_service.fetch_areas() │ │ │ │ │ │ 3. terrain_service.ensure_tiles() │ │ │ │ │ │ 4. GPU precomputation (if available) │ │ │ ├── precompute_distances() │ │ │ ├── precompute_path_loss() │ │ │ ├── batch_terrain_los() │ │ │ └── batch_antenna_pattern() │ │ │ │ │ │ 5. Point loop (parallel or sequential) │ │ │ │ │ │ │ │ For each grid point: │ │ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ a. Use precomputed distance │ │ │ │ │ │ b. Use precomputed path_loss │ │ │ │ │ │ c. Use precomputed antenna_loss │ │ │ │ │ │ d. Building LOS check │◄──┼──┼── BOTTLENECK │ │ │ e. Material penetration loss │ │ │ │ │ │ f. Dominant path analysis │◄──┼──┼── BOTTLENECK │ │ │ g. Street canyon loss │ │ │ │ │ │ h. Vegetation attenuation │ │ │ │ │ │ i. Reflection paths │ │ │ │ │ │ j. Water reflection │ │ │ │ │ │ k. Rain attenuation │ │ │ │ │ │ l. Indoor penetration │ │ │ │ │ │ m. Atmospheric loss │ │ │ │ │ │ n. Combine all losses │ │ │ │ │ │ o. Final RSRP │ │ │ │ │ └─────────────────────────────────┘ │ │ │ │ │ │ │ └── Parallel: ProcessPoolExecutor │ │ │ - 4-6 workers │ │ │ - Data pickled per worker │ │ │ - ~100MB pickle overhead each │ │ │ │ │ │ 6. Collect results, compute stats │ │ └──────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────┐ │ CoverageResult │ │ - points: CoveragePoint[] (lat, lon, rsrp, ...) │ │ - stats: min/max/avg, LOS%, model distribution │ │ - computation_time, models_used │ │ - boundary: contour polygon │ └────────────────────────┬────────────────────────┘ │ ┌─────────────┘ ▼ ┌─────────────────────────────────────────────────┐ │ WebGL Rendering │ │ │ │ WebGLRadialCoverageLayer (preferred): │ │ - Pass 1: Point accumulation (Gaussian radial) │ │ - Pass 2: Normalize + colormap │ │ - Instanced rendering: 1 draw call for all pts │ │ │ │ WebGLCoverageLayer (texture): │ │ - Grid to texture │ │ - Quintic Hermite interpolation │ │ - Single-pass colormap │ └─────────────────────────────────────────────────┘ ``` ### 1.4 Performance Bottleneck Analysis #### Critical Bottlenecks (Line-Level Detail) **1. Dominant Path Service (`dominant_path_service.py:155-350`)** The `find_dominant_paths_vectorized()` function is the #1 bottleneck: ```python # Line 208-214: Building filtering per point line_buildings = _filter_buildings_by_distance( line_buildings, (tx_lat, tx_lon), (rx_lat, rx_lon), max_count=MAX_BUILDINGS_FOR_LINE, # 30 max_distance=MAX_DISTANCE_FROM_PATH, # 200m ) # Line 230-234: Building to array conversion (allocates new arrays per point!) walls_start, walls_end, wall_to_bldg, poly_x, poly_y, poly_lengths = ( _buildings_to_arrays(line_buildings, ref_lat, ref_lon) ) # Line 300-307: Vectorized reflection search (still expensive) refl_point, refl_length, refl_loss = find_best_reflection_path_vectorized( tx, rx, r_walls_start, r_walls_end, r_wall_to_bldg, r_poly_x, r_poly_y, r_poly_lengths, max_candidates=30, max_walls=100, max_los_checks=5, ) ``` **Timing breakdown (from logs):** - Building query: 0.5-2ms per point - Array conversion: 1-5ms per point - LOS polygon test: 0.5-2ms per point - Reflection search: 5-20ms per point (when LOS blocked) - **Total: 10-30ms per point with buildings** For 6,700 points (50m resolution, 10km): **67-200 seconds just for dominant path** **2. ProcessPoolExecutor Overhead (`parallel_coverage_service.py`)** ```python # Each worker receives a pickled copy of: # - buildings list: 10-50MB (5000+ buildings with geometry) # - terrain cache: Referenced but requires re-mmap # - vegetation areas: 1-10MB # - water bodies: 1-5MB # Line 180-195: Data serialization chunk_data = { 'grid_chunk': grid_points[start:end], 'site_params': site_params, 'settings': settings, 'buildings': buildings, # FULL COPY 'streets': streets, 'water_bodies': water_bodies, 'vegetation_areas': vegetation_areas, } ``` **Memory impact:** 4 workers × 100MB pickle = 400MB overhead just for parallelism **3. Terrain Service (`terrain_service.py:180-232`)** Bilinear interpolation is called per point: ```python def _bilinear_sample(self, tile: np.ndarray, lat: float, lon: float) -> float: # 20+ float operations per sample # Not vectorized - called in Python loop ``` For terrain LOS checks: 50 samples × 6,700 points = 335,000 calls **4. Building Intersection (`dominant_path_service.py:674-690`)** ```python def _line_intersects_building_3d(self, ...): # Line 682: 20 samples per line segment for t in np.linspace(0, 1, 20): # Point-in-polygon test per sample if buildings_service.point_in_building(lat, lon, building): if height < building.height: return True ``` **5. Python GIL and Async Overhead** - ProcessPoolExecutor requires pickling all data - asyncio event loop adds latency for every await - NumPy releases GIL but building intersection code doesn't ### 1.5 Memory Usage Patterns | Component | Memory (10km) | Memory (50km) | Notes | |-----------|--------------|---------------|-------| | Grid points | 2MB | 50MB | 6,700 → 167,000 points | | SRTM tiles | 25-100MB | 100-400MB | 1-16 tiles × 25MB | | Buildings | 10-100MB | 50-500MB | Geometry arrays | | Worker copies | 100-400MB | 400-2GB | Pickle per worker | | GPU buffers | 50-200MB | 200-800MB | CuPy arrays | | **Total** | **200-800MB** | **800MB-4GB** | OOM likely at 50km | --- ## Part 2: Target Architecture ### 2.1 Rust Backend Structure ``` src-tauri/ ├── Cargo.toml # Workspace + deps ├── src/ │ ├── main.rs # Tauri app entry │ ├── lib.rs # Library exports │ ├── commands/ # Tauri IPC commands │ │ ├── mod.rs │ │ ├── coverage.rs # #[tauri::command] calculate_coverage │ │ ├── terrain.rs # Elevation queries │ │ ├── projects.rs # Project CRUD │ │ └── system.rs # System info, GPU detection │ │ │ ├── coverage/ # Coverage calculation engine │ │ ├── mod.rs │ │ ├── engine.rs # Main calculation orchestrator │ │ ├── grid.rs # Grid generation (adaptive) │ │ ├── point.rs # Per-point calculation │ │ ├── precompute.rs # Vectorized precomputation │ │ └── result.rs # CoveragePoint, CoverageResult │ │ │ ├── propagation/ # RF propagation models │ │ ├── mod.rs │ │ ├── traits.rs # PropagationModel trait │ │ ├── free_space.rs # FSPL │ │ ├── okumura_hata.rs # 150-1500 MHz │ │ ├── cost231_hata.rs # 1500-2000 MHz │ │ ├── cost231_wi.rs # Street canyon │ │ ├── itu_r_p1546.rs # Field strength model │ │ ├── longley_rice.rs # VHF │ │ └── knife_edge.rs # ITU-R P.526 diffraction │ │ │ ├── geometry/ # Geometry operations │ │ ├── mod.rs │ │ ├── haversine.rs # Great-circle distance (SIMD) │ │ ├── los.rs # Line-of-sight (vectorized) │ │ ├── intersection.rs # Ray-polygon (SIMD) │ │ ├── reflection.rs # Specular reflection │ │ └── spatial_index.rs # R-tree or grid index │ │ │ ├── terrain/ # Terrain data management │ │ ├── mod.rs │ │ ├── srtm.rs # SRTM tile loading (mmap) │ │ ├── cache.rs # LRU tile cache │ │ ├── sample.rs # Bilinear interpolation (SIMD) │ │ └── profile.rs # Elevation profiles │ │ │ ├── osm/ # OpenStreetMap data │ │ ├── mod.rs │ │ ├── client.rs # Overpass API client │ │ ├── building.rs # Building struct + parsing │ │ ├── cache.rs # SQLite OSM cache │ │ └── spatial.rs # Spatial queries │ │ │ ├── environment/ # Environmental effects │ │ ├── mod.rs │ │ ├── materials.rs # Building materials/penetration │ │ ├── vegetation.rs # Forest attenuation │ │ ├── water.rs # Water reflection │ │ ├── weather.rs # Rain attenuation │ │ ├── indoor.rs # Indoor penetration │ │ └── atmospheric.rs # O2/H2O absorption │ │ │ ├── gpu/ # GPU compute (optional) │ │ ├── mod.rs │ │ ├── wgpu_backend.rs # wgpu compute shaders │ │ ├── shaders/ │ │ │ ├── propagation.wgsl # Path loss compute │ │ │ ├── terrain_los.wgsl # Batch LOS check │ │ │ └── antenna.wgsl # Antenna pattern │ │ └── fallback.rs # CPU SIMD fallback │ │ │ ├── db/ # Data persistence │ │ ├── mod.rs │ │ ├── sqlite.rs # SQLite for projects/cache │ │ └── schema.rs # Database schema │ │ │ └── utils/ │ ├── mod.rs │ ├── progress.rs # Progress reporting │ └── units.rs # Unit conversions │ └── build.rs # Build script (shader compilation) ``` ### 2.2 SvelteKit Frontend Structure ``` src/ ├── app.html # HTML template ├── app.css # Global styles (Tailwind) ├── routes/ │ ├── +layout.svelte # Root layout │ └── +page.svelte # Main app page │ ├── lib/ │ ├── components/ │ │ ├── map/ │ │ │ ├── MapContainer.svelte # Leaflet wrapper │ │ │ ├── SiteMarker.svelte # Site markers │ │ │ ├── CoverageLayer.svelte # WebGL coverage (port shaders) │ │ │ ├── ElevationLayer.svelte │ │ │ ├── TerrainProfile.svelte │ │ │ ├── MeasurementTool.svelte │ │ │ └── Legend.svelte │ │ │ │ │ ├── panels/ │ │ │ ├── SiteList.svelte # Site management │ │ │ ├── BatchEdit.svelte # Batch operations │ │ │ ├── Settings.svelte # Coverage settings │ │ │ ├── LinkBudget.svelte # Link budget analysis │ │ │ ├── History.svelte # Calculation history │ │ │ └── Export.svelte # Export options │ │ │ │ │ ├── modals/ │ │ │ ├── SiteConfig.svelte # Site edit modal │ │ │ └── Confirm.svelte # Confirmation dialog │ │ │ │ │ └── ui/ │ │ ├── Button.svelte │ │ ├── Input.svelte │ │ ├── Select.svelte │ │ ├── Toast.svelte │ │ └── ThemeToggle.svelte │ │ │ ├── stores/ │ │ ├── sites.ts # Site state (Svelte stores) │ │ ├── coverage.ts # Coverage state │ │ ├── settings.ts # UI settings │ │ ├── history.ts # Undo/redo │ │ └── projects.ts # Project state │ │ │ ├── tauri/ │ │ ├── commands.ts # Tauri command wrappers │ │ ├── events.ts # Event listeners │ │ └── types.ts # Shared types │ │ │ ├── webgl/ │ │ ├── radial-layer.ts # WebGL radial gradients (port) │ │ ├── texture-layer.ts # WebGL texture (port) │ │ └── shaders.ts # GLSL shaders (reuse) │ │ │ ├── rf/ │ │ ├── fspl.ts # Client-side FSPL │ │ └── antenna.ts # Antenna patterns │ │ │ └── utils/ │ ├── colors.ts # Color utilities │ └── geo.ts # Geographic helpers │ ├── static/ │ └── icons/ # App icons │ └── tests/ └── ... ``` ### 2.3 Tauri IPC Design #### Commands (Rust → JS) ```rust // src-tauri/src/commands/coverage.rs #[tauri::command] async fn calculate_coverage( app: AppHandle, sites: Vec, settings: CoverageSettings, ) -> Result { // Spawns calculation on Rayon thread pool // Emits progress events via app.emit_all() } #[tauri::command] fn cancel_coverage(app: AppHandle) -> Result<(), String> { // Sets cancellation flag } #[tauri::command] async fn get_elevation(lat: f64, lon: f64) -> Result { // Single point elevation } #[tauri::command] async fn get_elevation_profile( lat1: f64, lon1: f64, lat2: f64, lon2: f64, num_points: u32, ) -> Result, String> { // Terrain profile } #[tauri::command] async fn fetch_osm_data( min_lat: f64, min_lon: f64, max_lat: f64, max_lon: f64, layers: Vec, ) -> Result { // Pre-fetch buildings, water, vegetation } ``` #### Events (JS → Rust progress updates) ```typescript // src/lib/tauri/events.ts import { listen } from '@tauri-apps/api/event'; interface ProgressPayload { phase: string; progress: number; // 0.0-1.0 eta_seconds?: number; tile?: { current: number; total: number }; } interface PartialResultPayload { points: CoveragePoint[]; tile_index: number; } export function listenCoverageProgress( onProgress: (p: ProgressPayload) => void, onPartial: (p: PartialResultPayload) => void, ) { const unlisten1 = listen('coverage:progress', e => onProgress(e.payload)); const unlisten2 = listen('coverage:partial', e => onPartial(e.payload)); return () => { unlisten1.then(f => f()); unlisten2.then(f => f()); }; } ``` #### Data Flow ``` ┌─────────────────────────────────────────────────────────────┐ │ SvelteKit Frontend │ │ │ │ coverageStore.calculate() │ │ │ │ │ ▼ │ │ invoke('calculate_coverage', { sites, settings }) │ │ │ │ │ listen('coverage:progress', updateProgress) │ │ listen('coverage:partial', addPartialPoints) │ │ │ │ └────────┼─────────────────────────────────────────────────────┘ │ Tauri IPC (JSON serialization, ~1ms) ▼ ┌─────────────────────────────────────────────────────────────┐ │ Rust Backend │ │ │ │ #[tauri::command] │ │ async fn calculate_coverage(...) { │ │ // Spawn on Rayon thread pool │ │ rayon::spawn(move || { │ │ // Parallel grid processing │ │ grid_points.par_chunks(256) │ │ .for_each(|chunk| { │ │ // SIMD-optimized point calculation │ │ let results = calculate_chunk(chunk); │ │ // Emit partial results │ │ app.emit_all("coverage:partial", results); │ │ }); │ │ }); │ │ } │ └─────────────────────────────────────────────────────────────┘ ``` ### 2.4 Data Management Strategy #### Terrain (SRTM) ```rust // Memory-mapped tiles with zero-copy access pub struct TerrainManager { tiles: HashMap, // Direct mmap, no copy lru: LruCache, // LRU tracking only } impl TerrainManager { pub fn get_elevation(&self, lat: f64, lon: f64) -> f32 { let tile = self.get_tile(lat, lon)?; // Direct pointer arithmetic on mmap'd data // SIMD-optimized bilinear interpolation simd_bilinear_sample(tile.as_ptr(), lat, lon) } pub fn get_elevations_batch(&self, coords: &[(f64, f64)]) -> Vec { // Process 8 points at a time with AVX2 coords.par_chunks(8) .flat_map(|chunk| simd_batch_sample(chunk)) .collect() } } ``` #### OSM Data ```rust // SQLite cache with FlatBuffers serialization pub struct OsmCache { db: Connection, // rusqlite } impl OsmCache { pub fn get_buildings(&self, bbox: BBox) -> Vec { // Query by spatial index // Deserialize with FlatBuffers (zero-copy) } pub fn get_or_fetch(&self, bbox: BBox) -> Vec { if let Some(cached) = self.get_buildings(bbox) { return cached; } // Fetch from Overpass, cache, return } } ``` #### Projects ```rust // SQLite for projects (simple, portable) pub struct ProjectStore { db: Connection, } // Schema CREATE TABLE projects ( id TEXT PRIMARY KEY, name TEXT NOT NULL, data BLOB, -- MessagePack serialized created_at INTEGER, updated_at INTEGER ); CREATE TABLE sites ( id TEXT PRIMARY KEY, project_id TEXT, data BLOB, FOREIGN KEY (project_id) REFERENCES projects(id) ); ``` --- ## Part 3: Migration Phases ### Phase 1: Tauri + SvelteKit Shell (Replace Electron) **Goal:** Working desktop app with same UI, backend still Python **Duration:** 2-3 weeks #### Tasks 1. **Initialize Tauri project** ```bash npm create tauri-app@latest rfcp-tauri -- --template svelte-ts ``` 2. **Port UI components to Svelte** - Start with layout (App.tsx → +page.svelte) - Port UI components (Button, Input, etc.) - Port map components (reuse Leaflet) - Port panels (SiteList, Settings, etc.) 3. **Port WebGL layers** - Copy GLSL shaders verbatim - Adapt TypeScript to Svelte (minimal changes) - WebGLRadialCoverageLayer → radial-layer.ts 4. **Implement Tauri commands as HTTP proxies** ```rust #[tauri::command] async fn calculate_coverage(...) -> Result { // Forward to Python backend via HTTP let response = reqwest::get("http://localhost:8090/api/coverage/calculate") .await?; Ok(response.json().await?) } ``` 5. **Port state management** - Zustand → Svelte stores - Keep same persistence (IndexedDB via idb) 6. **Test parity** - All features working - Same performance as Electron version **Deliverable:** RFCP desktop app on Tauri, Python backend, ~20MB bundle --- ### Phase 2: Rust Propagation Engine (The Big Win) **Goal:** RF calculations in Rust, 50-100x faster for geometry-heavy operations **Duration:** 4-6 weeks #### Tasks 1. **Core geometry in Rust** ```rust // Haversine with SIMD (8 distances per instruction) pub fn haversine_batch( site: (f64, f64), points: &[(f64, f64)], ) -> Vec { points.par_chunks(8) .flat_map(|chunk| simd_haversine(site, chunk)) .collect() } // Line-polygon intersection with SIMD pub fn line_intersects_polygon_batch( line: (Point, Point), polygons: &[Polygon], ) -> Vec { // Vectorized edge tests } ``` 2. **Propagation models** ```rust pub trait PropagationModel: Send + Sync { fn calculate(&self, input: &PropagationInput) -> PropagationOutput; fn calculate_batch(&self, inputs: &[PropagationInput]) -> Vec; } // FSPL: trivial, ~1 nanosecond per point // Okumura-Hata: ~10 nanoseconds per point // COST-231: ~15 nanoseconds per point ``` 3. **Dominant path in Rust** ```rust pub fn find_dominant_path( tx: Point3D, rx: Point3D, buildings: &SpatialIndex, ) -> DominantPath { // R-tree query: O(log n) let nearby = buildings.query_line(tx.to_2d(), rx.to_2d()); // SIMD polygon tests let hits = simd_line_polygon_batch(&nearby, tx, rx); // Vectorized reflection search if !hits.is_empty() { find_best_reflection_simd(&nearby, tx, rx) } else { DominantPath::Direct } } ``` 4. **Parallel grid processing** ```rust pub fn calculate_coverage( sites: &[Site], settings: &CoverageSettings, on_progress: impl Fn(f32), ) -> CoverageResult { let grid = generate_grid(sites, settings); // Rayon parallel iteration let points: Vec = grid .par_iter() .enumerate() .map(|(i, coord)| { if i % 1000 == 0 { on_progress(i as f32 / grid.len() as f32); } calculate_point(coord, sites, settings) }) .collect(); CoverageResult { points, stats: compute_stats(&points) } } ``` 5. **Benchmark against Python** - Target: 10x faster for basic (terrain only) - Target: 50x faster for full (buildings + dominant path) **Deliverable:** Rust coverage engine, 50km calculations possible --- ### Phase 3: Rust Terrain/OSM Services (Offline-First) **Goal:** Complete data layer in Rust, no Python dependency **Duration:** 3-4 weeks #### Tasks 1. **Terrain service** ```rust pub struct TerrainService { data_dir: PathBuf, tiles: RwLock>, download_queue: Mutex>, } impl TerrainService { pub async fn ensure_tiles(&self, bbox: BBox) -> Result<()> { let needed = self.get_needed_tiles(bbox); for tile in needed { if !self.has_tile(tile) { self.download_tile(tile).await?; } } Ok(()) } } ``` 2. **OSM client with caching** ```rust pub struct OsmService { client: reqwest::Client, cache: SqliteCache, } impl OsmService { pub async fn get_buildings(&self, bbox: BBox) -> Vec { if let Some(cached) = self.cache.get_buildings(bbox) { return cached; } let response = self.client .post("https://overpass-api.de/api/interpreter") .body(overpass_query(bbox, "building")) .send() .await?; let buildings = parse_overpass_response(response); self.cache.store_buildings(bbox, &buildings); buildings } } ``` 3. **Spatial indexing** ```rust // R-tree for fast spatial queries pub struct BuildingIndex { rtree: RTree, } impl BuildingIndex { pub fn query_line(&self, p1: Point, p2: Point) -> Vec<&Building> { let envelope = LineString::new(vec![p1, p2]).envelope(); self.rtree .locate_in_envelope(&envelope) .filter(|b| b.geometry.intersects_line(p1, p2)) .collect() } } ``` 4. **Project persistence** - SQLite database for projects - Binary serialization (MessagePack or bincode) 5. **Remove Python backend** - All functionality in Rust - Single executable **Deliverable:** Self-contained Rust app, no Python, ~10MB bundle --- ### Phase 4: Advanced Features (GPU Compute, 50km+) **Goal:** Handle massive calculations with GPU acceleration **Duration:** 4-6 weeks #### Tasks 1. **wgpu compute shaders** ```wgsl // propagation.wgsl @group(0) @binding(0) var grid_points: array>; @group(0) @binding(1) var site: vec4; // lat, lon, height, freq @group(0) @binding(2) var path_loss: array; @compute @workgroup_size(256) fn main(@builtin(global_invocation_id) id: vec3) { let idx = id.x; if (idx >= arrayLength(&grid_points)) { return; } let point = grid_points[idx]; let dist = haversine(site.xy, point); // COST-231 Hata let freq = site.w; let height = site.z; path_loss[idx] = cost231_hata(dist, freq, height); } ``` 2. **Tiled GPU processing** ```rust pub async fn calculate_coverage_gpu( sites: &[Site], settings: &CoverageSettings, gpu: &GpuContext, ) -> CoverageResult { let grid = generate_grid(sites, settings); // Process in tiles that fit in GPU memory let tile_size = 50_000; // 50k points per tile let mut all_points = Vec::new(); for tile in grid.chunks(tile_size) { // Upload to GPU let grid_buffer = gpu.create_buffer(tile); // Dispatch compute shader gpu.dispatch_propagation(grid_buffer, sites); gpu.dispatch_terrain_los(grid_buffer, terrain); // Read back results let results = gpu.read_buffer(grid_buffer); all_points.extend(results); } CoverageResult { points: all_points, ... } } ``` 3. **Streaming results** - Emit partial results per tile - Progressive rendering in UI 4. **Memory-mapped result caching** - Cache calculation results - Instant re-render on settings change **Deliverable:** 50km+ calculations, GPU-accelerated, <30 second full preset --- ## Part 4: Component Migration Map ### Backend Services | Current File | Action | Target | Notes | |-------------|--------|--------|-------| | `coverage_service.py` | REWRITE | `src-tauri/src/coverage/engine.rs` | Rayon parallelism, SIMD geometry | | `parallel_coverage_service.py` | DELETE | N/A | Replaced by Rayon | | `terrain_service.py` | REWRITE | `src-tauri/src/terrain/srtm.rs` | mmap + SIMD bilinear | | `buildings_service.py` | REWRITE | `src-tauri/src/osm/building.rs` | R-tree index | | `los_service.py` | REWRITE | `src-tauri/src/geometry/los.rs` | Vectorized LOS | | `dominant_path_service.py` | REWRITE | `src-tauri/src/coverage/dominant_path.rs` | SIMD intersection tests | | `materials_service.py` | REWRITE | `src-tauri/src/environment/materials.rs` | Direct port | | `street_canyon_service.py` | REWRITE | `src-tauri/src/environment/street_canyon.rs` | Direct port | | `reflection_service.py` | REWRITE | `src-tauri/src/geometry/reflection.rs` | SIMD specular calc | | `vegetation_service.py` | REWRITE | `src-tauri/src/environment/vegetation.rs` | Direct port | | `water_service.py` | REWRITE | `src-tauri/src/environment/water.rs` | Direct port | | `weather_service.py` | REWRITE | `src-tauri/src/environment/weather.rs` | Direct port | | `indoor_service.py` | REWRITE | `src-tauri/src/environment/indoor.rs` | Direct port (trivial) | | `atmospheric_service.py` | REWRITE | `src-tauri/src/environment/atmospheric.rs` | Direct port | | `spatial_index.py` | REWRITE | `src-tauri/src/geometry/spatial_index.rs` | Use `rstar` crate | | `geometry_vectorized.py` | REWRITE | `src-tauri/src/geometry/intersection.rs` | SIMD with `packed_simd2` | | `gpu_service.py` | REWRITE | `src-tauri/src/gpu/wgpu_backend.rs` | wgpu compute shaders | | `osm_client.py` | REWRITE | `src-tauri/src/osm/client.rs` | reqwest + serde | | `cache.py` | REWRITE | `src-tauri/src/db/sqlite.rs` | rusqlite | ### Propagation Models | Current File | Action | Target | Notes | |-------------|--------|--------|-------| | `free_space.py` | REWRITE | `src-tauri/src/propagation/free_space.rs` | Trivial port | | `okumura_hata.py` | REWRITE | `src-tauri/src/propagation/okumura_hata.rs` | Same formulas | | `cost231_hata.py` | REWRITE | `src-tauri/src/propagation/cost231_hata.rs` | Same formulas | | `cost231_wi.py` | REWRITE | `src-tauri/src/propagation/cost231_wi.rs` | Same formulas | | `itu_r_p1546.py` | REWRITE | `src-tauri/src/propagation/itu_r_p1546.rs` | Same formulas | | `longley_rice.py` | REWRITE | `src-tauri/src/propagation/longley_rice.rs` | Same formulas | | `itu_r_p526.py` | REWRITE | `src-tauri/src/propagation/knife_edge.rs` | Same formulas | ### Geometry | Current File | Action | Target | Notes | |-------------|--------|--------|-------| | `geometry/haversine.py` | REWRITE | `src-tauri/src/geometry/haversine.rs` | SIMD batch version | | `geometry/los.py` | REWRITE | `src-tauri/src/geometry/los.rs` | Vectorized | | `geometry/diffraction.py` | REWRITE | `src-tauri/src/geometry/diffraction.rs` | Same formulas | | `geometry/intersection.py` | REWRITE | `src-tauri/src/geometry/intersection.rs` | SIMD | | `geometry/reflection.py` | REWRITE | `src-tauri/src/geometry/reflection.rs` | SIMD | ### Frontend Components | Current File | Action | Target | Notes | |-------------|--------|--------|-------| | `App.tsx` | PORT | `src/routes/+page.svelte` | Svelte syntax | | `components/map/Map.tsx` | PORT | `src/lib/components/map/MapContainer.svelte` | Svelte + Leaflet | | `components/map/WebGLRadialCoverageLayer.tsx` | PORT | `src/lib/webgl/radial-layer.ts` | Keep shaders verbatim | | `components/map/WebGLCoverageLayer.tsx` | PORT | `src/lib/webgl/texture-layer.ts` | Keep shaders verbatim | | `components/map/SiteMarker.tsx` | PORT | `src/lib/components/map/SiteMarker.svelte` | Svelte syntax | | `components/map/*.tsx` | PORT | `src/lib/components/map/*.svelte` | Svelte syntax | | `components/panels/SiteList.tsx` | PORT | `src/lib/components/panels/SiteList.svelte` | Svelte syntax | | `components/panels/*.tsx` | PORT | `src/lib/components/panels/*.svelte` | Svelte syntax | | `components/modals/*.tsx` | PORT | `src/lib/components/modals/*.svelte` | Svelte syntax | | `components/ui/*.tsx` | PORT | `src/lib/components/ui/*.svelte` | Svelte syntax | | `store/sites.ts` | PORT | `src/lib/stores/sites.ts` | Svelte stores | | `store/coverage.ts` | PORT | `src/lib/stores/coverage.ts` | + Tauri commands | | `store/*.ts` | PORT | `src/lib/stores/*.ts` | Svelte stores | | `services/api.ts` | REWRITE | `src/lib/tauri/commands.ts` | Tauri invoke() | | `services/websocket.ts` | REWRITE | `src/lib/tauri/events.ts` | Tauri events | | `hooks/*.ts` | PORT | `src/lib/hooks/*.ts` | Svelte stores | | `rf/*.ts` | PORT | `src/lib/rf/*.ts` | Direct copy | | `types/*.ts` | PORT | `src/lib/tauri/types.ts` | Merge + simplify | ### Desktop | Current File | Action | Target | Notes | |-------------|--------|--------|-------| | `desktop/main.js` | DELETE | N/A | Tauri handles this | | `desktop/preload.js` | DELETE | N/A | Tauri IPC | | `desktop/splash.html` | PORT | `src-tauri/src/splashscreen.rs` | Optional | | Electron-builder config | REWRITE | `src-tauri/tauri.conf.json` | Tauri config | --- ## Part 5: Risk Assessment ### 5.1 Hard to Port | Component | Difficulty | Risk | Mitigation | |-----------|------------|------|------------| | Dominant path geometry | High | Complex vectorization | Extensive testing, benchmark vs Python | | SRTM bilinear interp | Medium | Numerical precision | Unit tests with known values | | Overpass API parsing | Medium | OSM data quirks | Use established crate (osmpbf) | | WebGL shaders | Low | Already GLSL | Copy verbatim | | Leaflet integration | Low | Same as React | svelte-leaflet exists | ### 5.2 Needs R&D | Topic | Effort | Notes | |-------|--------|-------| | wgpu compute shaders | 2-3 weeks | Learn wgpu, write propagation shaders | | SIMD geometry | 1-2 weeks | Use `packed_simd2` or `std::simd` | | R-tree spatial index | 1 week | Evaluate `rstar` vs custom | | SQLite FTS for OSM | 1 week | May need RTree extension | ### 5.3 Minimum Viable Product (MVP) **Phase 1 MVP (Tauri shell):** - SvelteKit UI with Leaflet - WebGL coverage rendering - Site management (CRUD, import/export) - Python backend via HTTP proxy - ~20MB bundle **Phase 2 MVP (Rust engine):** - Basic propagation (FSPL, Okumura-Hata, COST-231) - Terrain LOS (no buildings yet) - 10km calculations in <10 seconds - ~12MB bundle **Phase 3 MVP (Full Rust):** - Buildings + dominant path - All propagation models - 20km full preset in <30 seconds - ~10MB bundle ### 5.4 Estimated Timeline | Phase | Duration | Cumulative | Dependencies | |-------|----------|------------|--------------| | Phase 1: Tauri Shell | 2-3 weeks | 3 weeks | None | | Phase 2: Rust Engine | 4-6 weeks | 9 weeks | Phase 1 | | Phase 3: Full Rust | 3-4 weeks | 13 weeks | Phase 2 | | Phase 4: GPU/50km | 4-6 weeks | 19 weeks | Phase 3 | **Total: ~4-5 months for full migration** ### 5.5 Key Dependencies (Rust Crates) ```toml [dependencies] # Tauri tauri = { version = "1.5", features = ["api-all"] } tauri-plugin-store = "0.1" # Async runtime tokio = { version = "1.0", features = ["full"] } # HTTP client reqwest = { version = "0.11", features = ["json"] } # Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rmp-serde = "1.1" # MessagePack # Database rusqlite = { version = "0.30", features = ["bundled"] } # Spatial rstar = "0.11" # R-tree geo = "0.27" # Geometry types # SIMD packed_simd2 = "0.3" # or std::simd on nightly # GPU (optional) wgpu = "0.18" # Parallelism rayon = "1.8" # Memory mapping memmap2 = "0.9" # Logging tracing = "0.1" tracing-subscriber = "0.3" ``` ### 5.6 Performance Targets | Scenario | Python Current | Rust Target | Speedup | |----------|---------------|-------------|---------| | 5km, fast preset | 5s | 0.5s | 10x | | 10km, standard | 60s | 3s | 20x | | 10km, detailed | 180s (timeout) | 10s | 18x | | 20km, detailed | timeout | 30s | ∞ | | 50km, full | OOM | 120s | ∞ | ### 5.7 Bundle Size Targets | Component | Python/Electron | Rust/Tauri | |-----------|----------------|------------| | Frontend | 10MB | 2MB (Svelte is smaller) | | Backend executable | 150MB (PyInstaller) | 8MB (Rust binary) | | Runtime | 10MB (Electron) | 5MB (WebView2 shared) | | **Total** | **170MB+** | **~15MB** | --- ## Appendix A: Critical Algorithms in Rust ### A.1 SIMD Haversine Distance ```rust use packed_simd2::f64x4; use std::f64::consts::PI; const EARTH_RADIUS: f64 = 6371000.0; #[inline] pub fn haversine_batch( site_lat: f64, site_lon: f64, lats: &[f64], lons: &[f64], ) -> Vec { assert_eq!(lats.len(), lons.len()); let n = lats.len(); let mut distances = vec![0.0; n]; let site_lat_rad = site_lat * PI / 180.0; let site_lon_rad = site_lon * PI / 180.0; let cos_site_lat = site_lat_rad.cos(); let sin_site_lat = site_lat_rad.sin(); // Process 4 points at a time with SIMD let chunks = n / 4; for i in 0..chunks { let base = i * 4; let lat_rad = f64x4::new( lats[base] * PI / 180.0, lats[base + 1] * PI / 180.0, lats[base + 2] * PI / 180.0, lats[base + 3] * PI / 180.0, ); let lon_rad = f64x4::new( lons[base] * PI / 180.0, lons[base + 1] * PI / 180.0, lons[base + 2] * PI / 180.0, lons[base + 3] * PI / 180.0, ); let dlat = lat_rad - f64x4::splat(site_lat_rad); let dlon = lon_rad - f64x4::splat(site_lon_rad); let sin_dlat_2 = (dlat * f64x4::splat(0.5)).sin_cos().0; let sin_dlon_2 = (dlon * f64x4::splat(0.5)).sin_cos().0; let a = sin_dlat_2 * sin_dlat_2 + f64x4::splat(cos_site_lat) * lat_rad.cos() * sin_dlon_2 * sin_dlon_2; let c = f64x4::splat(2.0) * a.sqrt().asin(); let dist = f64x4::splat(EARTH_RADIUS) * c; distances[base..base + 4].copy_from_slice(&dist.to_array()); } // Handle remainder for i in chunks * 4..n { distances[i] = haversine_scalar(site_lat, site_lon, lats[i], lons[i]); } distances } ``` ### A.2 Parallel Grid Calculation ```rust use rayon::prelude::*; pub fn calculate_coverage_parallel( sites: &[Site], settings: &CoverageSettings, terrain: &TerrainService, buildings: &BuildingIndex, progress: impl Fn(f32) + Sync, ) -> Vec { let grid = generate_grid(sites, settings); let total = grid.len(); let counter = std::sync::atomic::AtomicUsize::new(0); grid.par_iter() .map(|coord| { let i = counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if i % 1000 == 0 { progress(i as f32 / total as f32); } calculate_point(coord, sites, settings, terrain, buildings) }) .collect() } ``` ### A.3 R-tree Spatial Index ```rust use rstar::{RTree, AABB, PointDistance}; pub struct BuildingIndex { rtree: RTree, } #[derive(Clone)] struct BuildingRef { id: u64, envelope: AABB<[f64; 2]>, height: f32, geometry: Vec<[f64; 2]>, } impl rstar::RTreeObject for BuildingRef { type Envelope = AABB<[f64; 2]>; fn envelope(&self) -> Self::Envelope { self.envelope } } impl BuildingIndex { pub fn new(buildings: Vec) -> Self { let refs: Vec = buildings.iter() .map(|b| BuildingRef { id: b.id, envelope: compute_envelope(&b.geometry), height: b.height, geometry: b.geometry.clone(), }) .collect(); Self { rtree: RTree::bulk_load(refs) } } pub fn query_line(&self, p1: [f64; 2], p2: [f64; 2]) -> Vec<&BuildingRef> { let envelope = AABB::from_corners( [p1[0].min(p2[0]) - 0.001, p1[1].min(p2[1]) - 0.001], [p1[0].max(p2[0]) + 0.001, p1[1].max(p2[1]) + 0.001], ); self.rtree .locate_in_envelope(&envelope) .filter(|b| line_intersects_polygon(p1, p2, &b.geometry)) .collect() } } ``` --- ## Appendix B: WebGL Shader Reuse The GLSL shaders from `WebGLRadialCoverageLayer.tsx` can be used directly in Svelte: ```typescript // src/lib/webgl/shaders.ts export const POINT_VERTEX_SHADER = ` attribute vec2 a_position; attribute vec2 a_pointPos; attribute float a_pointRsrp; attribute float a_pointRadius; varying vec2 v_localPos; varying float v_rsrp; void main() { vec2 pos = a_pointPos + a_position * a_pointRadius; gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0); v_localPos = a_position; v_rsrp = a_pointRsrp; } `; export const POINT_FRAGMENT_SHADER = ` precision highp float; varying vec2 v_localPos; varying float v_rsrp; void main() { float dist = length(v_localPos); if (dist > 1.0) discard; float weight = exp(-dist * dist * 2.0); gl_FragColor = vec4(weight * v_rsrp, weight, 0.0, 1.0); } `; // ... rest of shaders ``` --- ## Appendix C: Tauri Configuration ```json // src-tauri/tauri.conf.json { "build": { "beforeBuildCommand": "npm run build", "beforeDevCommand": "npm run dev", "devPath": "http://localhost:5173", "distDir": "../build" }, "package": { "productName": "RFCP", "version": "2.0.0" }, "tauri": { "bundle": { "active": true, "targets": ["nsis", "msi", "app", "dmg", "deb", "appimage"], "identifier": "one.eliah.rfcp", "icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.icns", "icons/icon.ico"], "resources": ["terrain/**/*"], "windows": { "wix": null } }, "windows": [ { "title": "RFCP - RF Coverage Planner", "width": 1400, "height": 900, "resizable": true, "fullscreen": false } ], "security": { "csp": null } } } ``` --- ## Summary This migration will transform RFCP from a Python/Electron application into a high-performance Rust/Tauri native app. Key benefits: 1. **Performance:** 20-50x faster calculations through Rust + SIMD + Rayon 2. **Size:** 10-15MB bundle vs 170MB+ current 3. **Memory:** Constant memory usage via mmap and streaming 4. **Capability:** 50km+ calculations that currently timeout The phased approach allows incremental delivery while maintaining functionality throughout the migration.