feat: satnavscii — terminal satnav with perspective rendering#1
Merged
Conversation
…E rewrite
Phase 3: gpsd integration with GPS jump filter and speed-dependent zoom.
Phase 4: Navigation HUD with speed, distance, ETA, turn-by-turn display
and corner minimap showing route overview.
Phase 4.5: Raspberry Pi kiosk mode (--kiosk flag + systemd service).
Also:
- Containerfile for podman/docker deployment
- GitHub Actions CI replacing Travis CI (Node 18/20/22 + container build)
- README rewrite with full CLI docs, paying homage to mapscii
- Remove stale .eslintrc.js, .travis.yml, snap/snapcraft.yaml, main.js
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auto-fixed (14): - Canvas.line() swapped width/color args - Promise.reject() without reason in both renderers - GPS jump filter time-based bypass (now uses speed-based threshold) - GPX parser time misalignment + attribute order assumption - TileSource cache size 16→64 for perspective renderer - TileSource caches broken tiles on load failure (now cache-after-load) - Zoom range validation for tile download (cap at 16) - Tile fetch timeout + HTTP status check - Speed-to-zoom discontinuity (now smooth log2 curve) - Containerfile reproducible tsx install - Compass heading negative normalization - GPXReplay interval leak on double-start - Animation timer leak on double-start User-approved fixes (5): - Config singleton mutation (only MapsciiConfig keys applied, createConfig added) - TileDownloader Nominatim rate limiting - Tile visibility cap centered on camera position - HTTPS for tile server URL - Config refactor (createConfig factory, no more global mutation) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- _draw() catch-all now only swallows "renderer busy", logs real errors - PerspectiveRenderer no longer overwrites shared TileSource styler - TileSource.init() now async, awaits loadMBTiles (was fire-and-forget race) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parts list, step-by-step setup from OS flash through test drive, offline tile download, GPS configuration, kiosk auto-start, mounting tips, and operational advice. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
rolldown (vitest's bundler) uses node:util.styleText which was added in Node 20. Updated engines field and CI matrix accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The minimap was only drawing the route polyline on a blank background. Now it reads cached tiles at a lower zoom level and renders roads, water, landuse etc. as a proper top-down overview. Also pre-fetches minimap tiles alongside main tiles so data is available by next frame. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Increased size from 20% to 30% width / 40% height - Roads drawn as connected polylines instead of individual pixels - Water/landuse drawn as fill point clouds - Skip noisy layers (buildings, labels, POIs) — only water, land, roads, admin - Proper coordinate transform with configurable view span - Route overlay drawn 2px wide for visibility - Position dot enlarged to 5x5 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Water: dark blue, Landuse: dark green, Roads: light gray, Admin: dim gray - Polygons (water, land) drawn as connected outlines, not scattered pixels - Roads drawn as connected polylines with proper layer-specific colors - Route: bright cyan 2px wide, Position: white 5x5 dot - Render order: fills first, lines on top, route on top, position last - Colors no longer inherited from tile styles (which were all white/gray) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fill features can have points as Point[] or Point[][] depending on geometry type. Now detects the shape before iterating, and guards against undefined/malformed point data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Minimap: - Now uses the SAME tiles already loaded by the main perspective view (guaranteed available, no separate low-zoom fetches that may not load) - Roads render as connected polylines using tile feature data at full zoom - Water/landuse drawn as polygon outlines - Dedicated color palette: blue water, green land, gray roads, cyan route - Shows 3x wider area than the main view for good overview context Vehicle arrow: - Classic GPS triangle arrow on the main perspective view (bottom-center, always pointing up since the map rotates around the vehicle) - Same arrow on the minimap, rotated to match camera heading - Arrow drawn as filled triangle in Braille sub-pixels Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…led arrow Minimap: - Now reads tile.data.layers (ALL features in each tile) instead of tile.layers (only features visible in the narrow perspective frustum). This is the root cause of "only seeing the route" — roads to the left, right, and behind the camera were being excluded. - Roads in all directions now visible as gray polylines on dark background. Vehicle arrow: - Now drawn as a proper filled triangle using scanline fill, not just edges. - Main view arrow: size 10 (was 6), bright cyan-green color (stands out). - Minimap arrow: size 6 (was 4), white, rotated by heading. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Removed minimap entirely (wasn't rendering roads properly) - Removed nav HUD overlay (speed, ETA, turn-by-turn) — deferred to issue - Vehicle arrow now projects from camera position so it stays on the road instead of being fixed at an arbitrary screen position - Arrow is larger (size 10) and filled with scanline rendering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ns in top half Arrow: - Fixed at bottom-center of screen, always visible. The vehicle doesn't move in a driving view — the map moves around it. Previous approach of projecting the camera position hit the near-plane and returned null. Directions: - Turn-by-turn instructions now rendered large in the top half of the terminal (above the horizon line where it's just empty sky). Drawn twice offset by 4 braille rows for a bold/thick effect. - Bright cyan-green color to stand out. Status bar remains small at the bottom (coords, heading, speed, ETA). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the hack of drawing text twice offset by 4px with actual 2x-wide characters: each letter is written twice at 2px offset to fill a 4px-wide cell instead of the normal 2px. Single line, no duplication, wider text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Arrow: - Finds the closest route point to the camera in map space - Projects that point to screen space and draws the arrow there - Arrow stays on the route as the camera moves along it - Falls back to bottom-center if no route or projection fails Direction text: - Fixed "AArrrriivveedd" bug caused by writing each character twice - Now uses normal canvas.text() centered in the top area above the horizon - Clean single rendering, no character duplication Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Arrow: - Fixed at bottom-center of screen like a real Garmin GPS. The map scrolls and rotates around the vehicle. The vehicle never moves. - Removed route-chasing logic that caused the arrow to jump around. Directions: - Filter out noise maneuvers: "new name", "depart", "straight continue" These produced "new straight name onto..." text that made no sense. - Only show actionable turns: left, right, fork, roundabout, exit, arrive - Human-readable formatting: "Turn left onto Friedrichstraße (200m)" instead of "turn left onto Friedrichstraße (200m)" - Added isActionableStep() filter with tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…mples Directions: - "(200m)" was the road segment length (useless). Now shows distance FROM your current position TO the upcoming turn: "In 200m, Turn left" - Matches how every real GPS displays turn-by-turn info - Range increased to 800m so turns show up earlier Examples: - Changed all example routes from Berlin to Jersey Channel Islands Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…y Hub 8.5km route across Jersey, 13 min drive. Both buildings geocode correctly via Nominatim. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Was advancing 1 route point per 100ms regardless of distance between points. A 220-point 8.5km route finished in 22 seconds (~1400 km/h). Now advances by distance: 14 m/s (50 km/h) at 200ms ticks = 2.8m per tick. The 8.5km Jersey route now takes ~10 minutes, matching the real 13-minute OSRM estimate. ETA countdown is now based on remaining distance / speed instead of point-count progress. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sky flickering: - Features were rendering above the horizon (screen.y between 0 and horizon). Now clips at horizon line, not y=0. Nothing draws in the sky area except the direction text. Route gap: - Route points near the camera were dropped by near-plane clipping, creating a gap between the route line and the vehicle arrow. Now clamps near-plane points to bottom-center of screen so the route always connects to the vehicle position. Speed: - Bumped from 50 km/h (too slow for a demo) to 120 km/h. The 8.5km Jersey route now takes ~4 minutes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Route overlay: - Only draws from current position forward (like a real GPS). Previously drew the entire route including passed segments, which caused multiple lines converging on the vehicle at the start. - Route line starts from the vehicle arrow position and extends ahead. - Behind-camera points are skipped, not clamped to bottom-center. Ctrl+C: - SIGINT handler cleans up timers, GPS, mouse, restores terminal state (cursor visibility, colors, raw mode) before exiting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Raw mode on stdin captures Ctrl+C as a keypress event (key.ctrl + key.name === 'c') instead of delivering SIGINT. Added handler in _onKey to catch it and call _cleanup() for a clean terminal exit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Recorded driving from Digital Jersey Exchange to Digital Jersey Hub. Converted from screen recording to 800px 12fps GIF (320KB). Added *.mov to .gitignore. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Recorded new demo at native resolution, converted to H.264 1080p (550KB, web-optimized with faststart). Much sharper than the 800px GIF. Embedded as HTML video tag with autoplay/loop/muted for GitHub. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Preserves original mapscii copyright holders, adds satnavscii contributors line for 2026. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…video tags GitHub markdown ignores <video> tags. Replaced MP4 with a 1200px wide GIF at 15fps quality 90 (736KB). Renders natively in README. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- All references updated from satnavscii to SatNavSCII - GitHub URLs point to hest-hq/SatNavSCII - Container registry ghcr.io/hest-hq/SatNavSCII - LICENSE updated with Hest attribution - Containerfile labels updated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
satnavscii: a terminal-based car navigation system built on mapscii's Braille rendering engine. Perspective-tilted maps, route navigation, GPS tracking, and turn-by-turn directions, all in Unicode Braille characters.
TypeScript Migration
Perspective Rendering (Phase 1)
PerspectiveRenderer.ts— Mode 7 projection with Camera stateCamera.ts— position, heading, pitch, FOV, near-plane clippingGPXReplay.ts— GPX track file parser and timed replayRoute Navigation (Phase 2)
RoutingService.ts— OSRM API + Nominatim geocoding (rate-limited, cached)--route "origin;destination"and--animateflagsOffline Tiles (Phase 2.5)
TileDownloader.ts— bulk region download with progress and rate limitingsatnavscii download --region "Berlin" --zoom 10-16GPS Tracking (Phase 3)
GPSInput.ts— gpsd integration with speed-based jump filter--gpsflagNavigation HUD + Minimap (Phase 4)
Raspberry Pi Kiosk (Phase 4.5)
--kioskflag (fullscreen, auto-GPS, no quit)satnavscii.servicesystemd unit fileInfrastructure
Bug Fixes (from 2 adversarial review passes, 23 issues fixed)
all/nonefilter logic was invertedTest Coverage
53 tests across 8 test files covering:
Pre-Landing Review
Two full adversarial review passes. 23 total issues found and fixed. 0 remaining.
Test plan
🤖 Generated with Claude Code