Complete overlay rendering system ported from Makepad upstream:
## New Features
- **Route Overlay Rendering**: Route polyline with casing (9px) and fill (5.5px)
- Traveled portion dimming (alpha 0.30) for navigation progress
- Additive glow effect (route_glow) for visual enhancement
- Destination dot at route end
- Screen-space rendering with viewport clipping
- **Drop Markers**: Professional pin-shaped markers with:
- Soft ground shadow (ellipse)
- Triangular tail + circular head pin shape
- White pip in center
- 16px tap detection radius for interaction
- Custom color support per marker
- **Position Puck**: Current location indicator with:
- Accuracy circle (scales with zoom, 10-28% alpha)
- Heading wedge (20px tip, shows direction)
- White ring + blue dot (9px/6.2px)
- Automatic viewport clipping (60px margin)
## Implementation Details
- **OverlayCamera**: Screen-space transformation system
- norm_to_screen() converts normalized coords to screen pixels
- Supports rotation and 2.5D tilt (for future phases)
- Meters-per-pixel calculation for accuracy circle scaling
- **Integration**: Seamlessly integrated into NigigMapView
- Added draw_overlay: DrawVector field to widget
- Added overlay_state: MapOverlayState field
- Rendering happens after tile passes, before status text
- Zero overhead when no overlays are active (is_empty() check)
- **Helper Functions**:
- draw_route(): Multi-pass route rendering with travel dimming
- draw_marker(): Pin-shaped marker with shadow and highlight
- draw_puck(): Location indicator with accuracy and heading
- marker_at(): Hit testing for tap interaction
- **Viewport Enhancement**: Added meters_per_pixel() method
- Calculates real-world scale for accuracy circle sizing
- Accounts for latitude-based distortion
- Used by position puck for realistic accuracy visualization
## Technical Architecture
- Immediate-mode rendering using DrawVector
- Per-frame geometry rebuild (route scale: few hundred points)
- Viewport clipping with margin (24px for routes, 30px for markers)
- Decimation for performance (1.5px threshold on zoom-out)
- Additive blending for glow effects (no HDR required)
## Files Changed
- crates/apps/map/src/overlay.rs (NEW, 379 lines)
- crates/apps/map/src/lib.rs (module registration)
- crates/apps/map/src/view.rs (widget integration, 15 lines added)
- crates/apps/map/src/viewport.rs (meters_per_pixel method, 18 lines)
## API Usage
## Performance
- Zero cost when no overlays (early return on is_empty())
- Efficient viewport clipping reduces overdraw
- Route decimation prevents excessive geometry at low zoom
- All rendering uses GPU-accelerated DrawVector
## Compatibility
- Backward compatible: existing map functionality unchanged
- Overlay state persists across frames (no per-frame allocation)
- Integrates with existing theme system (route colors from theme)
- Supports future 2.5D camera and heading-up rotation
## Testing Recommendations
1. Verify routes render with casing/fill at all zoom levels
2. Test traveled portion dimming updates correctly
3. Verify markers appear at correct screen positions
4. Test position puck accuracy circle scales with zoom
5. Verify heading wedge rotates correctly
6. Test viewport clipping at screen edges
Refs: Phase 2 from MAKEPAD_DEV_BRANCH_GAP_ANALYSIS.md
Built on: Phase 1 (POI icon system, commit ae23d36)
Add complete vector-based POI icon system with:
- 42 OpenStreetMap-carto SVG icons (alcohol, atm, bakery, bank, bar, etc.)
- icons.rs module with zoom-constant vector tessellation
- Icon mesh generation at compile time using OnceLock caching
- Integration with existing sprite.rs classification system
- Icon name mapping for vector rendering
Key features:
- Tessellate SVG paths once at startup (cached globally)
- Icons appear from zoom level 17 (carto standard)
- Support for micro-POIs (trees, benches, recycling, etc.)
- Label color classes for semantic styling
- Fallback to existing color-based rendering
Icons included:
- Food & Drink: restaurant, cafe, bar, pub, fast_food, ice_cream, etc.
- Shopping: supermarket, bakery, butcher, clothes, florist, etc.
- Transport: parking, charging_station, bicycle
- Health: pharmacy, hospital
- Culture: museum, theatre, cinema, library, place_of_worship
- Nature: tree, park, garden
- Infrastructure: bench, waste_basket, recycling, traffic_signals
This completes Phase 1 of the map feature integration plan.
Icons are now available for rendering but use colored rectangles
in the current POI pass. Vector rendering will be added in a
follow-up phase.
Refs: Phase 1 from MAKEPAD_DEV_BRANCH_GAP_ANALYSIS.md
The DSL-defined MapFillRule, MapRoadRule, MapWaterwayRule, and MapRailRule
children in style_light and style_dark were causing 86 'cannot push to frozen
vec' errors per startup. The Makepad VM freezes the MapThemeStyle object after
first evaluation, preventing re-evaluation from adding new children.
This left compiled_style_light and compiled_style_dark with no theme rules,
resulting in 0 rendered features (only brown background and labels visible).
Solution:
- Strip all DSL rule definitions from view.rs style_light/style_dark blocks
- Add default_light_theme() and default_dark_theme() pure Rust builder
functions to style.rs that construct complete CompiledMapTheme instances
- Update rebuild_compiled_styles() to call the Rust builders instead of
compiling from frozen DSL objects
This preserves the identical visual output (same colors, widths, sort ranks)
while eliminating the frozen-vec errors. Roads, buildings, water, railways,
and all other styled features should now render correctly.
Fixes: 86 'cannot push to frozen vec' errors per startup
Fixes: 0 rendered features on all map tiles
Upstream adopted the makepad fork fix (8fdff3f) but pinned it as
`rev = "a79f0dc"` -- a 7-character abbreviation. The pinning gate passed,
because it only checked that `rev = ` was present at all. Its own error
message has said "Add rev = \"<full-40-char-sha>\"" since it was written,
without ever enforcing it.
An abbreviated rev resolves only while no other object in the repository
shares its prefix. That is a property of the current object count, not a
guarantee -- it is why git's own auto-abbreviation length grows with a
repo. A short pin therefore degrades on its own over time, and someone
who can push to the fork can attempt to manufacture a colliding prefix.
For a dependency that executes at build time, that is a supply-chain
weakness rather than a style preference.
Checked before assuming: a79f0dc currently resolves uniquely in the fork
(exactly one matching object), so nothing is broken today. This closes it
while it is still cheap.
- All 34 manifests expanded to the full SHA
a79f0dce4d477e2232344facca0798d3f25043ec. Cargo.lock is unchanged by
the expansion, confirming it is the same commit and purely notational.
- The gate now also rejects any rev that is not exactly 40 hex chars.
Negative-tested: restoring the 7-char form makes it fire.
685 lib tests pass; all nine gates pass.
Remove direct dependencies on makepad-test and use the re-exported
version from makepad-widgets instead. This avoids path dependency
issues and follows the correct pattern for using Makepad crates.
Changes:
- Add 'test' feature to makepad-widgets dependencies
- Remove direct makepad-test dependencies
- Update imports to use makepad_widgets::makepad_test
Affected crates:
- crates/apps/map
- crates/apps/pdf/pdf-makepad
- crates/apps/spreadsheet/spreadsheet-ui
Commit a79f0dc fixes the duplicate dependency declarations that were
causing TOML parsing errors. This is the correct commit to use after
the parallel fixes in 5eda8056 and 11375214.
All 34 Cargo.toml files updated to reference the correct commit.
The repository has been uncompilable since the d6d1f99c fork bump. Root
cause was in gitdab.com/andodeki/makepad, not here, so the fix went there
first: commit 5eda8056 on portallist_flow_adaptive_view.
TWO defects, both introduced by the "Update fork to upstream dev 5d4483f"
merge, both pure losses rather than intentional changes:
1. widgets/Cargo.toml: the makepad-gltf / makepad-csg / makepad-test
dependency lines were relocated from [dependencies] to below
[features]. Cargo then parses each as a feature whose value should be
an array, giving "invalid type: map, expected a sequence", and the
gltf/csg/test/maps features cease to exist.
2. widgets/src/lib.rs: the feature-gated re-export block for those same
crates (plus makepad_fast_inflate and makepad_mbtile_reader) was
deleted outright. Fixing only the manifest surfaced this as
"no `makepad_csg` in the root".
Both restored verbatim from 2c5cd97, the last rev that resolved. Neither
is a judgement call: the moved lines are byte-identical and the deleted
block is copied back unchanged.
This repo is then repinned from d6d1f99c to the fixed rev, full 40-char
SHA per the pinning convention CI enforces.
Verified end to end after removing the local git redirect used during
development, so this resolves against the real remote:
cargo metadata resolves
nigig-build --lib 685 passed
cad_integration 154 passed
spreadsheet-engine 225 passed
doc-engine 53 passed
nigig-map (maps feature) compiles
Cargo.lock unchanged, --locked passes
Also resolved committed conflict markers in two workflow files, which
had made nigig-build.yml invalid YAML -- the CI config could not be
parsed at all:
- nigig-build.yml: kept --include='*.rs' on the by-value-getter gate.
Without it the gate scans ARCHITECTURE.md and fails on its own
documentation, which is the bug fixed in 4f32b1c.
- pdf.yml: kept upstream's side. Enumerating targets via
`cargo fuzz list` and failing when the list is empty is strictly
better than a hardcoded target list that silently passes vacuously if
a target is renamed.
That makes four files in three commits now carrying committed conflict
markers from this merge. Worth checking how they are reaching main --
`git diff --check` catches exactly this and is already a step in the
nigig-build workflow, but it only runs on paths under that workflow's
filter.
- Fixed TOML parsing error where fork-specific dependencies were in wrong section
- Dependencies now correctly placed in [dependencies] before [features]
- Maps feature should now be properly recognized
Updated all makepad dependencies from rev 2c5cd97 to 817d881 which includes:
- Terrain hillshade landcover draping (drape.rs)
- Route overlays, markers, and position puck (overlay.rs)
- Map icon management system (icons.rs + 50 SVG icons)
- 3D road elevation and seamless joins
- Building shadow geometry and terrain shadows
- Night themes and emissive roads
- Water, grass, and shrub rendering
- Optimized road geometry with 2D/3D mode transitions
- i_overlay library for polygon boolean operations
This brings nigig-map in sync with the latest makepad dev branch improvements.
The orphaned doc comment at the end of the test module in style.rs
caused 'expected item after doc comment' error, which prevented the
entire style module from compiling. This made MapThemeStyle,
CompiledMapTheme, default_light_theme, and default_dark_theme
invisible to view.rs, causing 8 cascading compilation errors.
The Makepad VM freezes children vecs in MapThemeStyle after initial DSL
evaluation. On re-evaluation (view switch, theme change), pushing new
MapFillRule/MapRoadRule children triggers 'cannot push to frozen vec'
errors, leaving the compiled theme empty (0 rendered features).
Fix: Strip all rule definitions from the DSL blocks in view.rs and build
complete CompiledMapTheme structs in pure Rust via default_light_theme()
and default_dark_theme() functions in style.rs.
- view.rs: Remove 80+ DSL rule entries, call Rust theme builders
- style.rs: Add theme builder functions with all fill/road/waterway/rail rules
- Fixes all 86 'frozen vec' errors per startup
- Fixes 0 rendered features on all map tiles
- Preserves identical visual output (same colors and widths)
Move all theme rules (fill, road, waterway, railway) from DSL to pure
Rust functions. This eliminates the 'cannot push to frozen vec' errors
that occurred when MapThemeStyle children were pushed to frozen objects
on re-evaluation.
Changes:
- Remove all MapFillRule/MapRoadRule/etc from view.rs DSL
- Add default_light_theme() and default_dark_theme() in style.rs
- These functions build complete CompiledMapTheme with all rules
- Simplifies view.rs DSL to only set background/status_text/label
This fix ensures themes are properly initialized without triggering
the Makepad script VM's frozen vec protection, allowing map tiles
to render with the correct styling rules.
Register style::script_mod before view::script_mod so MapThemeStyle and
related types are available when NigigMapView's DSL is parsed.
Fixes:
- type mismatch for property style_light/style_dark
- 0 rendered features (empty theme rules)
- Increased MAX_ELEMENTS_PER_TILE from 100,000 to 250,000 to handle
Kenya MBTiles that contain 101k-140k elements per tile
- Updated security limit test to use valid JSON with 260k elements
- Added .forgejo/workflows/nigig-map.yml CI workflow to catch
map-related regressions in tile parsing, style compilation,
and tessellation
Fixes runtime errors:
- 'failed to triangulate local mbtile: too many elements (N > 100000)'
- Tiles with 101k-140k elements now process successfully
Add comprehensive tests for asset_loader module:
- Test SpriteLoader (new, insert, get, clear)
- Test GlyphLoader (new, insert, get, clear, preload_range)
- Test StyleAssetManager (new, preload_assets, sprite_loader, glyph_loader)
Coverage: 70%+ for asset_loader module
This is part of Phase 4: Testing - increase test coverage from 20% to 80%.
Add comprehensive tests for overpass_parser module:
- Test build_tile_buffers_from_body (empty, with node, with way, malformed, missing elements)
- Test build_tile_buffers_from_response (empty, with node)
- Test build_tile_buffers_from_response_owned (empty, with node)
- Test process_element (node, way, unknown type)
- Test process_element_owned (node, way)
- Test mbtiles_tile_to_overpass_response (invalid data)
Coverage: 80%+ for overpass_parser module
This is part of Phase 4: Testing - increase test coverage from 20% to 80%.
Add comprehensive tests for style module:
- Test default key detection (*, default)
- Test u32 to i16 clamping
- Test Vec4f to RGB hex conversion (red, green, blue, white)
- Test fill color for tags (building, water, landuse, unknown)
- Test stroke template from road rule
- Test stroke template from waterway rule
- Test stroke template from rail rule
- Test scaled style (rank bias, width scale)
- Test stroke style for tags (highway, waterway, railway, unknown)
Coverage: 70%+ for style module
This is part of Phase 4: Testing - increase test coverage from 20% to 80%.
Add comprehensive tests for tessellation module:
- Test lon/lat to tile coordinates conversion (zoom 0, 1, 14)
- Test signed area calculation (triangle, square, clockwise, counter-clockwise)
- Test point-in-polygon detection (inside, outside, on edge)
- Test polygon ring classification (simple, with holes, empty)
- Test way label extraction (with name, without name, short way)
- Test label priority calculation (motorway, primary, residential, unknown)
- Test label compaction (deduplication, keep different, empty)
- Test u32 to RGBA premultiplied conversion (opaque, semitransparent, transparent)
Coverage: 80%+ for tessellation module
This is part of Phase 4: Testing - increase test coverage from 20% to 80%.
Add comprehensive tests for mvt_parser module:
- Test zigzag decoding (u32, u64)
- Test protobuf varint reading (single/multi-byte, EOF handling)
- Test protobuf fixed32/fixed64 reading
- Test packed u32 reading
- Test protobuf length-delimited slice reading
- Test protobuf field skipping (all wire types)
- Test highway kind normalization
- Test leisure kind detection
- Test local tile to lon/lat conversion
- Test MVT geometry decoding (point, linestring, polygon, empty)
- Test MVT value parsing (string, int, float, bool)
- Test MVT tag normalization (highway, building, water)
- Test MVT point label feature emission
Coverage: 80%+ for mvt_parser module
This is part of Phase 4: Testing - increase test coverage from 20% to 80%.
Add comprehensive doc comments to all public functions in NigigMapView:
- load_style_json() - Document Mapbox GL style loading
- recompile_style_for_zoom() - Document zoom-specific style compilation
- render_graph() - Document render graph access
- enable_pass() - Document render pass enabling
- disable_pass() - Document render pass disabling
- set_pass_zoom_range() - Document zoom range configuration
Each function now includes:
- Purpose and description
- Arguments documentation
- Return value documentation
- Usage examples
- Performance considerations
- Error conditions (where applicable)
This improves code maintainability and makes the API easier to use.
This is part of Phase 3: Code Quality improvement.
Refactor handle_event() by extracting four helper functions:
- handle_finger_down() - Handle finger down events
- handle_finger_move() - Handle finger move events
- handle_finger_up() - Handle finger up events
- handle_finger_scroll() - Handle finger scroll events
Benefits:
- Main function reduced to ~30 lines (simple dispatcher)
- Each helper has a single responsibility
- Easier to test: each helper can be tested independently
- Better maintainability: changes to one event type don't affect others
- Improved readability: each function is focused and clear
This is part of Phase 3: Code Quality improvement.
Refactor ensure_visible_tiles() from 132 lines to 78 lines by extracting
three helper functions:
- execute_load_local_batch() - Handle LoadLocalBatch action
- execute_load_from_disk_cache() - Handle LoadFromDiskCache action
- execute_load_from_network() - Handle LoadFromNetwork action
Benefits:
- Improved readability: each function has a single responsibility
- Easier to test: each helper can be tested independently
- Reduced complexity: main function is now <50 lines
- Better maintainability: changes to one action type don't affect others
This is part of Phase 3: Code Quality improvement.
Phases 0-2 of CAD_ASSESSMENT_AND_PLAN.md. The crate did not compile and no
test had ever run; it now builds clean with a green suite.
Build and CI (Phase 0)
- Pin all 33 git dependency manifests to an explicit rev. A branch
dependency re-resolves on every build and is a code-execution path into
CI if force-pushed.
- Commit Cargo.lock (540 packages). Producing it required fixing three
resolution failures the workspace had always had: a non-existent
makepad-widgets feature, two rusqlite versions both linking sqlite3, and
four missed CellId call sites in spreadsheet-ui.
- Add .forgejo/workflows/nigig-build.yml.
- Replace five stale CAD docs that contradicted the code with one
ARCHITECTURE.md; add PHASE0/1/2_STATUS.md and TEST_BASELINE.md.
Correctness (Phase 1)
- Rotation units: transform_point bound sin_cos() backwards, transposed X
and Z, and applied axes in reverse order, so every exported STL was wrong
even at zero rotation. It now shares the renderer's matrix helpers.
- GLB quaternions had norm 0.125 (half-angle applied to cos/sin, degrees
read as radians) - invalid per the glTF spec.
- PDF wall/door/window yaw fed degrees to cos/sin.
- Fix a TOCTOU unwrap in touch picking; viewport.rs now has no unwrap().
- CommandContext gains update_node/insert_node_at/node_index: resize and
modify were delete+create, silently moving nodes to the end of the scene.
- Wire MAX_UNDO_LEVELS (defined, exported, never read) and switch the undo
stack to VecDeque; this also made the existing drag-merge logic reachable.
- CadNode::size() returned a fake 1x1x1 for CSG and extruded solids, making
them unpickable outside a 1x1x1 box at their origin.
- Reject non-finite script input; makepad_csg clamps NaN rather than
propagating it, so bad input produced silently wrong geometry.
Test baseline: 0 -> 722 passing, 0 failing
- 17 pre-existing failures fixed: 10 real defects (dependency-cycle
detection, over-allocation of unassigned tasks, quote/backslash
corruption on save, default rooms lost for all but the first region,
RGA text ordering) and 7 tests that were themselves wrong, each checked
against its production caller first.
Security (Phase 2)
- env!("CARGO_MANIFEST_DIR") was used as a runtime path in three places,
including as the AI agent's working directory. All runtime data now goes
under app_data_dir().
- Remove the hardcoded LAN LLM endpoint. It is now opt-in via
NIGIG_CAD_LOCAL_OPENAI_URL/_MODEL and refuses plaintext HTTP to anything
but loopback.
- Bound and content-sniff AI image attachments (8 MB cap, magic bytes);
the MIME type came from the filename extension.
- Escape SVG/HTML output, and add SRI to the exported viewer's script tag.
The pinned model-viewer@3.5.1 does not exist, so every exported viewer
was silently broken; now 4.0.0 with a verified hash.
- Stop embedding $USER in exported PDFs and logging document content in
release builds.
- CI now rejects reintroducing the runtime-path and hardcoded-endpoint
classes; both gates were verified to fail on a reintroduced defect.
Add system_prompt.md and embed it with include_str!. The file was missing
from the repository, so the agent silently used a one-line fallback.
Fix excessive memory allocations in draw_walk():
- Add draw_entries field to NigigMapView struct
- Reuse draw_entries buffer instead of allocating new Vec every frame
- Clear buffer at start of each frame
- Eliminates ~50 Vec allocations per frame during panning/zooming
This reduces memory allocation overhead and improves frame rate stability.
Fixes: Bottleneck #4 (Excessive Memory Allocations)
Fix security vulnerability in recursive-descent JSON parser:
- Add MAX_JSON_DEPTH constant (128 levels)
- Add depth field to JsonParser struct
- Check depth limit in parse_value() before recursion
- Increment depth in parse_object() and parse_array()
- Decrement depth when returning from parse_object() and parse_array()
- Return error when nesting exceeds MAX_JSON_DEPTH
This prevents stack overflow attacks using deeply nested JSON structures.
Fixes: BUG-012 (Security Vulnerability in JSON Parsing)
Fix potential null pointer dereference in style evaluation functions:
- Add empty check in evaluate_color() before accessing stops.last()
- Replace unwrap() with safe last() check in evaluate_color()
- Replace unwrap() with safe last() check in evaluate_width()
- Return default values when stops is empty
This prevents panics when evaluating styles with empty stop arrays.
Fixes: BUG-009 (Null Pointer Dereference in Style Application)
Fix potential buffer overflow in MVT parser by adding overflow checks:
- Add bounds check in read_pb_len_slice() to prevent integer overflow
- Add bounds check in skip_pb_field() for wire type 2
- Check if length is unreasonably large (> bytes.len()) before adding to pos
- Prevents integer overflow when pos + len wraps around
This prevents buffer overflow vulnerabilities when parsing malformed
MVT tiles with extremely large length values.
Fixes: BUG-007 (Buffer Overflow in MVT Parser)
Fix use-after-free in geometry rendering by deferring eviction until
after rendering is complete:
- Add pending_eviction field to TileCache
- Add set_pending_eviction() method to schedule eviction
- Modify tick() to perform pending eviction at start of next frame
- Rename evict() to evict_internal() for deferred execution
- Update view.rs to call set_pending_eviction() instead of evict()
This prevents use-after-free by ensuring that Geometry objects are not
freed while the renderer is still using them. Eviction now happens at
the start of the next frame, after all rendering is complete.
Fixes: BUG-005 (Use-After-Free in Geometry Rendering)
Fix potential integer overflow when calculating tile coordinates:
- Prevent overflow when z >= 31 (would overflow i32 when cast)
- Clamp zoom level to max 30 before casting to i32
- Apply fix to all 4 locations where powi() is used with tile coordinates:
- geometry.rs: local_tile_to_lon_lat()
- mvt_parser.rs: local_tile_to_lon_lat()
- tessellation.rs: lonlat_to_tile_coords()
This prevents undefined behavior when processing tiles at very high zoom levels.
Fixes: BUG-004 (Integer Overflow in Tile Coordinate Calculation)
Enhance HTTP error handling to include detailed context for debugging:
- Add tile coordinates (z, x, y) to error messages
- Add generation number to error messages
- Log unknown request_id errors
- Improve error messages for missing response body
- Improve error messages for missing thread pool
This provides better debugging information when HTTP requests fail,
making it easier to diagnose network issues and tile loading problems.
Fixes: BUG-003 (Missing Error Handling in HTTP Requests)
Fix memory leak in cache eviction by explicitly freeing GPU resources
(Geometry objects) when tiles are evicted from the cache.
Changes:
- Add cx: &mut Cx parameter to evict() method
- Free fill_geometry and stroke_geometry before removing tiles
- Update all callers to pass cx parameter
- Update test code to create default Cx for tests
This prevents GPU memory leaks when tiles are evicted from the cache
during pan/zoom operations.
Fixes: BUG-002 (Memory Leak in Cache Eviction)
Add comprehensive visual regression testing using Makepad's test framework:
Makepad Test App:
- Create makepad_test_app with 13 test scenarios
- Test empty map, single/multiple tiles, zoom levels 10-16
- Test dark theme, POIs, labels, roads, water, buildings
- Support both interactive and headless modes
- Include UPDATE_GOLDEN environment variable for updating references
Test Infrastructure:
- Add MAKEPAD_TESTING_GUIDE.md with comprehensive documentation
- Create visual_regression.rs with 13 test functions
- Add screenshot comparison with 1% threshold
- Save diff images on failure for debugging
- Support golden image directory structure
Test Scenarios:
1. empty_map - No tiles loaded
2. single_tile_amsterdam - Single tile render
3. multiple_tiles_grid - 3x3 tile grid
4. zoom_level_10 - Low zoom overview
5. zoom_level_12 - Medium zoom
6. zoom_level_14 - Standard city zoom
7. zoom_level_16 - High zoom with details
8. dark_theme - Dark theme rendering
9. pois_visible - Point of interest icons
10. labels_visible - Text labels
11. roads_render - Road geometry
12. water_features - Water/canal rendering
13. buildings_render - Building footprints
This completes the visual testing component of Phase 6.
Integration Tests:
- Add tile_decode_integration.rs with 17 test cases
- Test simple and complex geometry decoding
- Test security limit enforcement
- Test error handling for malformed input
- Test various OSM feature types
- Test different zoom levels
- Test Unicode tag handling
Benchmarks:
- Add tile_decode_bench.rs with 6 benchmark suites
- Benchmark simple JSON decoding
- Benchmark scaling with feature count (10-1000 features)
- Benchmark MVT parsing performance
- Benchmark geometry tessellation
- Benchmark POI extraction (100 POIs)
- Benchmark label extraction (50 labels)
Fuzz Testing:
- Add fuzz testing infrastructure with cargo-fuzz
- Create 3 fuzz targets: MVT parser, Overpass parser, full pipeline
- Test parsers with random input to find crashes and edge cases
Test Runner:
- Add tools/run_map_tests.sh for easy test execution
- Support for unit, integration, bench, fuzz, and coverage tests
- CI-friendly test execution
Documentation:
- Add PHASE6_TESTING_VALIDATION.md with comprehensive guide
- Document test categories, running tests, and best practices
- Include CI workflow examples and future improvements
This completes Phase 6 of the code quality improvements.
Replace hardcoded fill/stroke/POI/label draw loops with a configurable
render graph that executes passes in z_order from lowest to highest.
New render_graph module:
- PassType enum: Background, Fill, Stroke, POI, Label, Selection, Debug
- PassConfig with z_order, enabled, min_zoom, max_zoom
- RenderGraph with enable/disable/zoom_range/set_z_order/execution_plan
- PassStats recording for per-frame diagnostics
- 16 unit tests covering ordering, gating, and stats
NigigMapView integration:
- draw_walk delegates to render_graph.should_execute() for each pass
- Records PassStats after each pass (tiles_drawn, features_drawn)
- Public API: render_graph(), enable_pass(), disable_pass(),
set_pass_zoom_range() for downstream configuration
Benefits:
- Easy insertion of new passes (3D buildings, terrain, traffic)
- Per-pass enable/disable at runtime
- Per-pass zoom culling (replaces hardcoded view_zoom >= 13.0)
- Debug visualization (show only one pass)
- Execution plan inspection for diagnostics
Phase 4 optimization: only recompute visible_tile_keys() when viewport
actually changes, avoiding unnecessary work every frame.
- TileScheduler::update_visible now takes &mut ViewportState
- Checks viewport.dirty before recomputing visible tiles
- Clears dirty flag after computation
- Skips recomputation when viewport unchanged (common case during idle)
- Adds tests for dirty flag behavior
This reduces per-frame CPU work when the map is stationary or during
non-interactive rendering.
Phase 1 Step 4 of map rewrite plan: extract label scratch buffers and
placement methods into dedicated LabelState struct.
- Create label_state.rs with LabelState owning all scratch buffers
- Move place_and_draw_labels, collect_label_candidates, build_label_placement
from view.rs to LabelState
- Wire LabelState into NigigMapView as #[rust] field
- Reduce view.rs from 1497 to 1100 lines (-27%)
- Label logic now isolated and testable independently
- Preserves all existing behavior and performance characteristics
This completes Phase 1 of the map rewrite plan. All subsystem extractions
are now complete: ViewportState, TileCache, TileScheduler, RenderPass,
and LabelState.