{"slug": "nakshatra-stargazing-in-total-darkness-because-phone-screens-blind-your-eyes", "title": "Nakshatra: Stargazing in Total Darkness (Because Phone Screens Blind Your Eyes)", "summary": "A developer built Nakshatra, a browser-based astronomical radar that guides users to real stars using acoustic cues and a near-black OLED display rather than a virtual sky map. The system uses a dual-oscillator sonar hum and whispered voice prompts to align the phone with celestial bodies, keeping the screen at 0.000 nits with deep-red telemetry above 650nm to preserve retinal rhodopsin, and runs a local Google Gemma model to narrate star lore. It splits targeting into a chest-level azimuth sweep and a chin-level altitude tilt to avoid arm fatigue.", "body_md": "*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass](https://dev.to/challenges/hacktoberfest-week1-2026-10-05)*\n\nMost software designed to help people disconnect from screens makes a contradictory mistake: it asks you to walk outside, pull out a glass rectangle, and look down at a screen to photograph leaves. It replaces desk-screen time with outdoor-screen time.\n\nI wanted something different. I wanted to step onto an urban rooftop at midnight, walk away from terminal windows, and look up at the actual sky.\n\nWhen you look at a smartphone in the dark, you encounter an immediate biological barrier. The human retina contains roughly 120 million rod photoreceptors that govern nocturnal night vision. These rods rely on a photopigment called rhodopsin. A single 500-millisecond glance at an illuminated smartphone screen bleaches over 80 percent of your retinal rhodopsin. Your pupils constrict from seven millimeters down to two. Rebuilding that dark adaptation requires 20 to 30 continuous minutes in total darkness.\n\nEvery mainstream astronomy app forces you to stare down at a digital simulation of stars rendered in glowing pixels. You stand under real celestial bodies while staring at a backlit slab.\n\nI built **Nakshatra** (the Sanskrit word for constellation or star sector).\n\nNakshatra is an eyes-up astronomical radar that runs inside the mobile browser. It does not display a virtual sky map. Its display is kept at **0.000 nits** (solid `#000000` on OLED panels) with low-intensity deep-red telemetry ($\\lambda > 650\\text{ nm}$) that preserves retinal rhodopsin.\n\nYou hold the phone flat against your chest. A dual-oscillator acoustic radar hums in your earbuds, rising in pitch as your torso turns toward an invisible celestial body. When you align with the star, a chime sounds, and a whispered voice directs you to bring the phone to your chin. You tilt your head back, look directly into the night sky, and listen as local Google Gemma whispers the lore of the star into your ears.\n\nYour eyes never leave the sky.\n\n*Figure 1: Nakshatra running in pitch darkness. On OLED panels, unused subpixels turn off completely (0.000 nits). Red elements use wavelengths above 650nm to protect rod photoreceptors.*\n\n`npm start --prefix client` (Runs at `http://localhost:3000`)\n*Figure 2: The two-phase posture: holding the phone flat at chest height to sweep the horizon, then raising it to the chin to gaze directly upward.*\n\n`npm test --prefix client`).\nBuilding for an idealized browser simulator is straightforward. Deploying that same software on a physical smartphone standing on a cold concrete roof reveals immediate hardware hurdles.\n\nHere is how the system is engineered from the ground up.\n\nHolding a smartphone upward at arm's length for two minutes causes rapid muscle fatigue and tremors. Nakshatra decouples horizontal azimuth alignment from vertical altitude targeting into two physical postures:\n\n```\ngraph LR\n    subgraph Phase_1 [\"Phase 1: Chest-Level Sweep\"]\n        direction TB\n        P1_Action[\"Phone flat in palm at chest (pitch <= 25°)\"]\n        P1_Sound[\"Acoustic sonar hums 220Hz-440Hz\"]\n        P1_Lock[\"Dwell within 6° for 1000ms locks heading\"]\n        P1_Voice[\"Whisper: 'Bring phone to your chin'\"]\n        P1_Action --> P1_Sound --> P1_Lock --> P1_Voice\n    end\n\n    subgraph Phase_2 [\"Phase 2: Chin-Level Tilt\"]\n        direction TB\n        P2_Action[\"Phone raised to chin (pitch > 25°)\"]\n        P2_Cue[\"Whisper: 'Tilt your head up slightly'\"]\n        P2_Lock[\"Pitch matches altitude within 3°\"]\n        P2_Voice[\"Lock chime: 'Stop right there'\"]\n        P2_Action --> P2_Cue --> P2_Lock --> P2_Voice\n    end\n\n    subgraph Phase_3 [\"Phase 3: Dark Observation\"]\n        direction TB\n        P3_Screen[\"Screen solid black (0.000 nits)\"]\n        P3_Gemma[\"Local Gemma whispers celestial lore\"]\n        P3_Screen --> P3_Gemma\n    end\n\n    P1_Voice --> P2_Action\n    P2_Voice --> P3_Screen\n```\n\nOn mobile Chromium on mid-tier Android devices, calling `event.acceleration` returns `{x: 0, y: 0, z: 0}` without throwing an error. The API appears active, but reports zero motion. Any code relying on linear acceleration to track physical movement fails silently.\n\nTo detect when a user is walking versus standing still, I read `event.accelerationIncludingGravity`, calculate the raw Euclidean magnitude, and strip Earth's gravitational acceleration:\n\n``` js\nhandleMotion(event) {\n  if (!event) return;\n\n  let dynamicMag = 0;\n  let hasLinearAcc = false;\n\n  if (event.acceleration && typeof event.acceleration.x === 'number' && event.acceleration.x !== null) {\n    const ax = Number(event.acceleration.x) || 0;\n    const ay = Number(event.acceleration.y) || 0;\n    const az = Number(event.acceleration.z) || 0;\n    const mag = Math.sqrt(ax * ax + ay * ay + az * az);\n    if (mag > 0.05) {\n      dynamicMag = mag;\n      hasLinearAcc = true;\n    }\n  }\n\n  if (!hasLinearAcc && event.accelerationIncludingGravity) {\n    const ax = Number(event.accelerationIncludingGravity.x) || 0;\n    const ay = Number(event.accelerationIncludingGravity.y) || 0;\n    const az = Number(event.accelerationIncludingGravity.z) || 0;\n    const rawMag = Math.sqrt(ax * ax + ay * ay + az * az);\n    dynamicMag = Math.abs(rawMag - 9.81);\n  }\n\n  const now = Date.now();\n  if (dynamicMag > 0.50 && (now - this._lastStepTime > 300)) {\n    this._lastStepTime = now;\n    this.stepsDetected += 1;\n    this.displacementMeters += 0.65;\n  }\n}\n```\n\nThis filter detects real footsteps and resets tracking if the user walks away from their observation point.\n\nSmartphone magnetometers experience continuous jitter between eight and twelve degrees caused by rooftop rebar and sensor noise. If you instruct an application to guide a user toward an exact single-degree coordinate, the audio radar chatters constantly.\n\nFurthermore, celestial targets between zero and fifteen degrees altitude are almost always obstructed by apartment buildings, power lines, or streetlights.\n\nI resolved both issues in `quadrant_selector.js`:\n\n```\nselectTargetForHeading(currentHeadingAz, allVisibleTargets) {\n  if (!allVisibleTargets || allVisibleTargets.length === 0) {\n    return null;\n  }\n\n  const currentQuad = this.getQuadrant(currentHeadingAz);\n\n  if (\n    this.latchedTarget &&\n    this.activeQuadrant === currentQuad &&\n    !this.isTargetCooledDown(this.latchedTarget.name)\n  ) {\n    return this.latchedTarget;\n  }\n\n  const inQuadrant = allVisibleTargets.filter((t) => {\n    if (typeof t.azimuth !== 'number' || typeof t.altitude !== 'number') {\n      return false;\n    }\n    if (t.altitude < this.minAltitudeDeg) {\n      return false;\n    }\n    return this.getQuadrant(t.azimuth) === currentQuad;\n  });\n\n  if (inQuadrant.length === 0) {\n    return null;\n  }\n\n  const uncooled = inQuadrant.filter((t) => !this.isTargetCooledDown(t.name));\n  const candidatePool = uncooled.length > 0 ? uncooled : inQuadrant;\n\n  const chosen = this._pickWeightedCandidate(candidatePool);\n  this.latchedTarget = chosen;\n  this.activeQuadrant = currentQuad;\n  return chosen;\n}\n```\n\nMy initial prototype used a strict 15-degree heading margin between the chest sweep and the chin tilt phase.\n\nIn outdoor testing, this caused nine out of ten attempts to abort. When a human raises their hands from chest level to their chin, the elbow hinge naturally rotates the wrist. This mechanical arc swings the internal phone compass by 25 to 35 degrees during transit.\n\nI relaxed the heading abort tolerance to 45 degrees during the lift motion and implemented a 1.2-second post-lift gyroscopic stabilization dampener. The software now conforms to human anatomy instead of treating the user like an industrial robotic arm.\n\nA core requirement of Nakshatra was to eliminate robotic coordinate readouts. Hearing a synthesized voice say *\"Azimuth 214 degrees, pitch 42 degrees, Right Ascension 18 hours\"* pulls the user out of contemplation and back into analytical tension.\n\n```\ngraph TD\n    subgraph Tier_1 [\"1. Mobile Sensor Hardening (50Hz)\"]\n        S_Orient[\"DeviceOrientation (Compass & Pitch)\"] --> S_Filter[\"Low-Pass Filter (alpha = 0.15)\"]\n        S_Motion[\"DeviceMotion (Raw Accelerometer)\"] --> S_Gravity[\"Gravity Decomposition Filter\"]\n        S_Filter --> S_Fusion[\"Calibrated Pose & Motion Vectors\"]\n        S_Gravity --> S_Fusion\n    end\n\n    subgraph Tier_2 [\"2. Astronomical Ephemeris Engine\"]\n        E_Catalog[\"80+ Celestial Body Catalog\"] --> E_Math[\"Sidereal & Topocentric Math\"]\n        S_Fusion --> E_Math\n        E_Math --> E_Quad[\"4-Quadrant Selector (18° Skyline Floor)\"]\n        E_Quad --> E_Latch[\"Target Latching & 30-Minute Cooldown\"]\n    end\n\n    subgraph Tier_3 [\"3. Intelligence & Audio Synthesis\"]\n        E_Latch --> A_Radar[\"Web Audio Radar (220Hz-440Hz)\"]\n        E_Latch --> W_Gemma[\"On-Device Gemma 3 (WebGPU Worker)\"]\n        W_Gemma -->|Memory limits| W_Lore[\"Deterministic Lore Database\"]\n        W_Gemma --> A_Speech[\"Sub-15ms Whispered Speech Synthesizer\"]\n        W_Lore --> A_Speech\n        A_Radar --> A_Speech\n    end\n```\n\nNakshatra uses a dual-engine architecture to generate body-relative celestial phrasing:\n\n`gemma-3-270m-it-ONNX` via Web Worker or Chrome's native Prompt API). All weight loading, token caching, and generation run off the main JavaScript thread, ensuring the audio oscillators never stutter.\nAll model outputs pass through a deterministic validation filter that checks for forbidden technical terms:\n\n``` js\nconst FORBIDDEN_JARGON_REGEX = /\\b(azimuth|altitude|right\\s+ascension|declination|degree[s]?|arcminute[s]?|arcsecond[s]?|bearing|southeast|northeast|southwest|northwest|fist[s]?|clenched)\\b/i;\n```\n\nUnit tests in an IDE pass easily. Real hardware outdoors fails in unexpected ways.\n\nTesting under real skies caught three critical edge-case bugs:\n\n`sensors.js` using shortest-angle modulo wrapping:\n\n``` js\nshortestAngleDiff(target, source) {\n  let diff = ((target - source + 180) % 360) - 180;\n  if (diff < -180) diff += 360;\n  return diff;\n}\n```\n\nThe 81 automated tests in `npm test` execute in 1.3 seconds via Node's native test runner, ensuring these mathematical edge cases remain guarded against regression.\n\n`Open Innovation` Matter?\nOpen-source and open-weight AI models are vital for tools designed for the physical outdoors.\n\nWhen you stand in a dark-sky preserve, on a mountain ridgeline, or on a remote trail, cellular towers do not reach you. A proprietary cloud application that depends on remote inference servers fails the moment connectivity drops.\n\nGoogle Gemma models running locally on client hardware fundamentally change outdoor software:", "url": "https://wpnews.pro/news/nakshatra-stargazing-in-total-darkness-because-phone-screens-blind-your-eyes", "canonical_source": "https://dev.to/nonebut_ak/nakshatra-stargazing-in-total-darkness-because-phone-screens-blind-your-eyes-3g93", "published_at": "2026-10-11 15:23:11+00:00", "updated_at": "2026-10-11 15:26:40.298620+00:00", "lang": "en", "topics": ["ai-tools", "generative-ai", "ai-products"], "entities": ["Nakshatra", "Google Gemma", "Hacktoberfest"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/nakshatra-stargazing-in-total-darkness-because-phone-screens-blind-your-eyes", "markdown": "https://wpnews.pro/news/nakshatra-stargazing-in-total-darkness-because-phone-screens-blind-your-eyes.md", "text": "https://wpnews.pro/news/nakshatra-stargazing-in-total-darkness-because-phone-screens-blind-your-eyes.txt", "jsonld": "https://wpnews.pro/news/nakshatra-stargazing-in-total-darkness-because-phone-screens-blind-your-eyes.jsonld"}}