Stand on the mountain — tap a panorama
The field day in pictures
Two courses hand-built by Ray Haller
Haller cut the Community course out of the hillside by hand over six and a half months; it is now the second-ranked course in New Mexico. Pick a course:
Under aspen and doug fir at the west end of Zenith Park, redesigned May 2025. Open Tiki on the map →
A wooded mountain track near the old baseball field, opened 2019, rated 4.6 stars from 500+ reviews. Open Community on the map →
The dataset
| Source | What it gives |
|---|---|
| USGS 3DEP 1 m DEM | One 248 MB tile covers both courses. Anchor elevations match the National Map to 0.1 ft — the elevation source of record for plays-like distances. |
| USGS NAIP 2022 60 cm imagery | One tile, cropped to a course window for the satellite toggle and draped over the 3D terrain. |
| OpenStreetMap trails | Live bbox extract: 46 paths + 26 footways with real trail names. The S3-native Daylight parquet path was attempted and rejected on measurement — no spatial partitioning; same data, bbox API, 20 seconds. |
| Ground GPS (Nov 1 survey) | Community holes 1–2 fully pinned (tee + basket averaged fixes, par/distance/mando notes, sign photos); hole-2 basket photographed; 6 unnamed Tiki points located. The rest is honest scaffolding until the next field day. |
| NOAA US Climate Normals 1991–2020 |
Cloudcroft station monthly normals, fetched live from the
Registry of Open Data bucket (noaa-normals-pds) —
the one Registry dataset this build pulls programmatically.
|

From phone to scorecard
Run the pipeline — three commands, real output:
The field procedure, same every course: high-accuracy 30-fix averages at each tee and basket, par + distance typed into the point note, tee-sign and basket photos with camera location tags on. The phone then uploads straight to a throwaway FTP dropbox on the home LAN — no cables, no Drive round-trip:
python scripts/swmaps_ftpd.py <password>
# serving dataset/survey/_incoming/ftp/ on 192.168.4.53:2121 as swmaps
SW Maps project backups carry every point, note, and attached photo. A small extractor turns them into open GeoJSON — no app lock-in:
python dataset/scripts/swm2_to_geojson.py community.swm2 Photos/ survey/2026-11-01-community
# wrote survey/2026-11-01-community/points.geojson: 21 points
Validation treats partial as a supported state — bad geometry fails, gaps are reported, never silently filled:
python dataset/scripts/validate_survey.py --partial
# 2026-11-01-community: 21 points — holes complete (tee+basket): [1];
# holes partial: [2]; unidentified points: 18
# partial survey ok: 3 named points, gaps are scaffolding
Then the adapter fills what the survey reached, scaffolds the rest, and enriches every point with its name, coordinates, and photo — so the next field day lights up holes with no code change:
python dataset/scripts/partial_holes.py
# community: cloudcroft-community.json (2 surveyed rows, 21 survey points)
# tiki: byron-ligon-tiki.json (0 surveyed rows, 6 survey points)
Serving it is a Rust Lambda (ARM64, sub-100 ms plays-like
math) behind API Gateway, all in one CDK stack. Agents verify the
live resources over MCP (call_aws) instead of
trusting the console:
// backend/plays-like/src/main.rs — the whole model
fn plays_like_ft(horizontal_ft: f64, elev_change_ft: f64) -> f64 {
if elev_change_ft >= 0.0 { horizontal_ft + elev_change_ft }
else { horizontal_ft + elev_change_ft * 0.5 }
}
// infra-cdk/lib/cloudcroft-disc-golf-stack.ts (assets built by
// scripts/build-lambda-assets.sh)
new lambda.Function(this, "PlaysLike", {
runtime: lambda.Runtime.PROVIDED_AL2023,
architecture: lambda.Architecture.ARM_64,
memorySize: 128,
handler: "bootstrap",
code: lambda.Code.fromAsset(
path.join(__dirname, "..", "assets", "plays-like"),
),
});
All 27 survey points, live
| Point | Kind | Coordinates | Elev (m) | Note | Photo |
|---|---|---|---|---|---|
| loading survey points… | |||||
Play the first two holes
Even through 2. Open the full card →
AWS services used and why
| Service | Role | Why this one |
|---|---|---|
| Amazon Location Service | Topo + imagery maps, place index, geofences, tracker, routes | One managed home for every spatial primitive; geofences trigger hole arrival on-site. |
| Amazon S3 | Dataset prefix + static hosting | Open-data tiles stage next to derived JSON; same bucket serves the bundle. |
| AWS Lambda (Rust, ARM64) | Plays-like distance + scorecard API | Sub-100 ms math at pennies per round. |
| Amazon DynamoDB | Scorecard rounds, on-demand | Schemaless rounds, zero ops, survives the phone in a pocket. |
| Amazon Cognito | Guest identity pool, one scoped role | No logins on the course; same role serves web and Android. |
| AWS CDK (TypeScript) | Whole stack as code | One deploy recreates everything. |
More mountain radio
On their home platforms: K-CLOUD (“Music for the Mountain”) on Live365 · KALH 95.1 Alamogordo · Red Dirt Rising podcast
Public-safety scanner feeds (open on Broadcastify): Cloudcroft Fire, Rescue & EMS + Otero County feeds
What is live, what is next
loading survey points…
The map and scorecard are live, as is the Cloud-Cam — reach both from the bar pinned to the bottom of this page. The repo stays private while the demo video is cut. Still to come: the under-3-minute demo video, then the Builder Center project post.

Day-two build underneath: anchors are geocoded, elevation comes from the USGS 3DEP 1-meter DEM, trails from OpenStreetMap, imagery from NAIP 2022. Community holes 1–2 carry ground pins; the rest land with the next survey — then AR overlays, plays-like distances, and per-hole geofences switch on.