This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Most 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.
I 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.
When 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.
Every 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.
I built Nakshatra (the Sanskrit word for constellation or star sector).
Nakshatra 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.
You 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.
Your eyes never leave the sky.
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.
Demo
- Live Web Application: https://IshekKhal.github.io/nakshatra
-
Local PWA Static Host:
npm start --prefix client(Runs athttp://localhost:3000)
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.
Code
- GitHub Repository: https://github.com/IshekKhal/nakshatra
-
Automated Test Suite: 81 unit tests passing across seven test suites in 1.3 seconds using the native Node.js test runner (
npm test --prefix client). - Software License: MIT License. Open source.
How I Built It
Building 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.
Here is how the system is engineered from the ground up.
1. The Two-Phase Ergonomic Radar
Holding 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:
graph LR
subgraph Phase_1 ["Phase 1: Chest-Level Sweep"]
direction TB
P1_Action["Phone flat in palm at chest (pitch <= 25°)"]
P1_Sound["Acoustic sonar hums 220Hz-440Hz"]
P1_Lock["Dwell within 6° for 1000ms locks heading"]
P1_Voice["Whisper: 'Bring phone to your chin'"]
P1_Action --> P1_Sound --> P1_Lock --> P1_Voice
end
subgraph Phase_2 ["Phase 2: Chin-Level Tilt"]
direction TB
P2_Action["Phone raised to chin (pitch > 25°)"]
P2_Cue["Whisper: 'Tilt your head up slightly'"]
P2_Lock["Pitch matches altitude within 3°"]
P2_Voice["Lock chime: 'Stop right there'"]
P2_Action --> P2_Cue --> P2_Lock --> P2_Voice
end
subgraph Phase_3 ["Phase 3: Dark Observation"]
direction TB
P3_Screen["Screen solid black (0.000 nits)"]
P3_Gemma["Local Gemma whispers celestial lore"]
P3_Screen --> P3_Gemma
end
P1_Voice --> P2_Action
P2_Voice --> P3_Screen
- Chest-Level Sweep: You hold the phone flat in your palm (pitch under 25 degrees). As you rotate your torso, a Web Audio oscillator hums between 220 Hz and 440 Hz. When your heading aligns with the target within six degrees for one second, a chime rings, and the audio prompts: "Heading locked. Bring phone to your chin."
- Chin-Level Tilt: You bring the phone up to your chin. Because your torso is already facing the correct quadrant, you only need to adjust vertical pitch. You tilt your chin up toward the zenith. The system monitors the pitch axis and whispers natural physical cues: "Tilt your head up slightly" or "Lower your gaze a bit."
- Pure Observation: When your angle matches within three degrees, a confirmation tone plays. The screen remains black, and local Google Gemma whispers the lore of the star.
2. Physical Sensor Hardening: The Gravity Decomposition Filter
On 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.
To detect when a user is walking versus standing still, I read event.accelerationIncludingGravity, calculate the raw Euclidean magnitude, and strip Earth's gravitational acceleration:
handleMotion(event) {
if (!event) return;
let dynamicMag = 0;
let hasLinearAcc = false;
if (event.acceleration && typeof event.acceleration.x === 'number' && event.acceleration.x !== null) {
const ax = Number(event.acceleration.x) || 0;
const ay = Number(event.acceleration.y) || 0;
const az = Number(event.acceleration.z) || 0;
const mag = Math.sqrt(ax * ax + ay * ay + az * az);
if (mag > 0.05) {
dynamicMag = mag;
hasLinearAcc = true;
}
}
if (!hasLinearAcc && event.accelerationIncludingGravity) {
const ax = Number(event.accelerationIncludingGravity.x) || 0;
const ay = Number(event.accelerationIncludingGravity.y) || 0;
const az = Number(event.accelerationIncludingGravity.z) || 0;
const rawMag = Math.sqrt(ax * ax + ay * ay + az * az);
dynamicMag = Math.abs(rawMag - 9.81);
}
const now = Date.now();
if (dynamicMag > 0.50 && (now - this._lastStepTime > 300)) {
this._lastStepTime = now;
this.stepsDetected += 1;
this.displacementMeters += 0.65;
}
}
This filter detects real footsteps and resets tracking if the user walks away from their observation point.
3. Horizon Partitioning and the 18° Urban Skyline Floor
Smartphone 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.
Furthermore, celestial targets between zero and fifteen degrees altitude are almost always obstructed by apartment buildings, power lines, or streetlights.
I resolved both issues in quadrant_selector.js:
- The sky is divided into four cardinal 90-degree quadrants: northeast (0° to 90°), southeast (90° to 180°), southwest (180° to 270°), northwest (270° to 360°).
- All celestial bodies below an 18.0-degree altitude cutoff are pruned from active consideration.
- Observed stars receive a 30-minute cooldown timestamp to prevent the app from locking onto the same bright object repeatedly.
selectTargetForHeading(currentHeadingAz, allVisibleTargets) {
if (!allVisibleTargets || allVisibleTargets.length === 0) {
return null;
}
const currentQuad = this.getQuadrant(currentHeadingAz);
if (
this.latchedTarget &&
this.activeQuadrant === currentQuad &&
!this.isTargetCooledDown(this.latchedTarget.name)
) {
return this.latchedTarget;
}
const inQuadrant = allVisibleTargets.filter((t) => {
if (typeof t.azimuth !== 'number' || typeof t.altitude !== 'number') {
return false;
}
if (t.altitude < this.minAltitudeDeg) {
return false;
}
return this.getQuadrant(t.azimuth) === currentQuad;
});
if (inQuadrant.length === 0) {
return null;
}
const uncooled = inQuadrant.filter((t) => !this.isTargetCooledDown(t.name));
const candidatePool = uncooled.length > 0 ? uncooled : inQuadrant;
const chosen = this._pickWeightedCandidate(candidatePool);
this.latchedTarget = chosen;
this.activeQuadrant = currentQuad;
return chosen;
}
4. Human Biomechanics: The 45° Wrist Transit Arc
My initial prototype used a strict 15-degree heading margin between the chest sweep and the chin tilt phase.
In 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.
I 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.
5. On-Device Google Gemma: Web Workers and Offline Lore
A 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.
graph TD
subgraph Tier_1 ["1. Mobile Sensor Hardening (50Hz)"]
S_Orient["DeviceOrientation (Compass & Pitch)"] --> S_Filter["Low-Pass Filter (alpha = 0.15)"]
S_Motion["DeviceMotion (Raw Accelerometer)"] --> S_Gravity["Gravity Decomposition Filter"]
S_Filter --> S_Fusion["Calibrated Pose & Motion Vectors"]
S_Gravity --> S_Fusion
end
subgraph Tier_2 ["2. Astronomical Ephemeris Engine"]
E_Catalog["80+ Celestial Body Catalog"] --> E_Math["Sidereal & Topocentric Math"]
S_Fusion --> E_Math
E_Math --> E_Quad["4-Quadrant Selector (18° Skyline Floor)"]
E_Quad --> E_Latch["Target Latching & 30-Minute Cooldown"]
end
subgraph Tier_3 ["3. Intelligence & Audio Synthesis"]
E_Latch --> A_Radar["Web Audio Radar (220Hz-440Hz)"]
E_Latch --> W_Gemma["On-Device Gemma 3 (WebGPU Worker)"]
W_Gemma -->|Memory limits| W_Lore["Deterministic Lore Database"]
W_Gemma --> A_Speech["Sub-15ms Whispered Speech Synthesizer"]
W_Lore --> A_Speech
A_Radar --> A_Speech
end
Nakshatra uses a dual-engine architecture to generate body-relative celestial phrasing:
-
Chrome Built-in Prompt API & WebGPU Web Worker: On supported devices, prompts are dispatched to on-device Google Gemma models (
gemma-3-270m-it-ONNXvia 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. - Progressive Fallback Lore Engine: If a device has limited memory or lacks WebGPU support, Nakshatra does not crash with an unhandled exception. It falls back to an internal celestial lore database containing observations across 80 navigational stars and deep-sky treasures.
All model outputs pass through a deterministic validation filter that checks for forbidden technical terms:
const 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;
6. Empirical Field Hardening: What Physical Testing Caught
Unit tests in an IDE pass easily. Real hardware outdoors fails in unexpected ways.
Testing under real skies caught three critical edge-case bugs:
-
Azimuth Boundary Wrapping: A direct subtraction across true North ($359^\circ \to 1^\circ$) produced a $-358^\circ$ error instead of a $+2^\circ$ delta. This bug sent the acoustic radar oscillator into an immediate high-frequency spike. I fixed this in
sensors.jsusing shortest-angle modulo wrapping:
shortestAngleDiff(target, source) {
let diff = ((target - source + 180) % 360) - 180;
if (diff < -180) diff += 360;
return diff;
}
- Target Dwell Hysteresis: Night breezes and natural body sway cause continuous minor heading fluctuations. Once a target latches, the ephemeris engine maintains that lock across 13.5 degrees of drift before releasing it, preventing target chatter.
- Rooftop Safety Displacement: If the accelerometer registers more than 3.5 meters of physical walking motion, the observation session resets immediately. Users must never walk across a dark rooftop while blind to their surroundings.
The 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.
Why Does Open Innovation Matter?
Open-source and open-weight AI models are vital for tools designed for the physical outdoors.
When 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.
Google Gemma models running locally on client hardware fundamentally change outdoor software:
- Complete Offline Autonomy: Nakshatra requires no cellular data, Wi-Fi, or remote backends. It functions equally well in a city park or in the high Himalayas.
- Uncompromising Sensory Privacy: No camera streams, coordinates, or audio data ever leave the device.
- Aligning Software with Human Biology: Commercial web applications rely on screen time to display advertisements. Open-weight models allow developers to build software that deliberately turns its own screen off and returns the user's attention to the night sky.
Prize Categories
- Best Use of Google DeepMind Gemma: On-device Web Worker running Gemma 3 (with Chrome Built-in Prompt API support) to generate body-relative celestial lore offline, backed by a deterministic fallback database.
Top comments (0)