tui-globe 0.0.0

Proof-of-concept terminal renderer for the Mullvad 3D globe data
Documentation
# tui-globe

A ratatui Braille-canvas globe widget. Geometry buffers under `assets/geo/`
are baked from public domain [Natural Earth](https://www.naturalearthdata.com)
data and embedded at compile time via `MapData::embedded()`.

## Regenerating the geometry

The `.gl` buffers are generated by `tools/build_geo.py`. The script fetches
shapefiles from naciscdn.org on demand, caches them, and writes the five
`.gl` blobs to `--out`. The buffers are checked in, so you only need to
re-run the script when bumping the data set or changing the build flags.

### Flags

| flag | default | meaning |
|---|---|---|
| `--scale {10m,50m,110m}` | `50m` | Natural Earth resolution. Smaller numbers = denser mesh / larger binary. |
| `--cultural-borders` / `--no-cultural-borders` | on | With borders, triangulate `ne_<scale>_admin_0_countries` (coastlines + country borders) and overlay `ne_<scale>_admin_1_states_provinces_lines`. Without, triangulate `ne_<scale>_land` (coastlines only). |
| `--data-dir PATH` | `$XDG_CACHE_HOME/tui-globe-build-geo` | Where to cache downloaded `.zip`/`.shp` files. |
| `--out PATH` | (required) | Output directory for the five `.gl` files. |
| `--ne-countries PATH` | auto-fetch | Override: custom `admin_0_countries .shp` (cultural mode). |
| `--ne-states PATH` | auto-fetch | Override: custom `admin_1_states_provinces_lines .shp` (cultural mode). |
| `--ne-land PATH` | auto-fetch | Override: custom physical land `.shp` (`--no-cultural-borders` mode). |

The override flags let you swap in locally modified shapefiles (e.g. simplified
geometry, custom border tweaks). They take precedence over auto-fetch for
that one input; the rest still come from the cache or are downloaded.

### Run it

The script is packaged as a Python project at `tools/pyproject.toml`,
declaring `pyshp`, `mapbox-earcut`, and `numpy` as dependencies. Pick
whichever workflow you prefer:

**With `uv`** (no venv management needed):

```bash
uv run --project tui-globe/tools build-geo --scale 50m --out tui-globe/assets/geo
```

**With `pip` into a venv:**

```bash
python3 -m venv .venv
.venv/bin/pip install ./tui-globe/tools
.venv/bin/build-geo --scale 50m --out tui-globe/assets/geo
```

**Without installing** (manual deps):

```bash
pip install pyshp mapbox-earcut numpy
python3 tui-globe/tools/build_geo.py --scale 50m --out tui-globe/assets/geo
```

### Examples

```bash
# Default: 50m with country and state/province borders
build-geo --scale 50m --out tui-globe/assets/geo

# High-detail land mesh with no borders
build-geo --scale 10m --no-cultural-borders --out tui-globe/assets/geo

# Tiny global-overview mesh
build-geo --scale 110m --out tui-globe/assets/geo

# Use custom / locally modified shapefiles (overrides auto-fetch)
build-geo --scale 50m \
    --ne-countries ./my-data/ne_50m_admin_0_countries.shp \
    --ne-states    ./my-data/ne_50m_admin_1_states_provinces_lines.shp \
    --out tui-globe/assets/geo

# Override just the country polygons; states still auto-fetched
build-geo --scale 50m \
    --ne-countries ./my-data/custom_countries.shp \
    --out tui-globe/assets/geo

# Custom physical-land shapefile, no borders
build-geo --no-cultural-borders \
    --ne-land ./my-data/custom_land.shp \
    --out tui-globe/assets/geo
```

### Verify

```bash
cargo test -p tui-globe
```

The test suite asserts the buffers parse to a thin unit-sphere shell, that
restart markers separate rings, and that no surviving chord exceeds the
0.05 threshold the renderer relies on.

## Output format

Five little-endian binary blobs, the same format the desktop Mullvad client
uploads into WebGL:

| file | dtype | meaning |
|---|---|---|
| `land_positions.gl` | `f32` xyz | unit-sphere vertices |
| `land_triangle_indices.gl` | `u32` | `GL_TRIANGLES` indices |
| `land_contour_indices.gl` | `u32` | `GL_LINE_STRIP` indices, `0xFFFFFFFF` between rings |
| `ocean_positions.gl` | `f32` xyz | level-4 icosphere vertices (2562) |
| `ocean_indices.gl` | `u32` | `GL_TRIANGLES` indices (5120 tris) |

Coordinate convention (matches `lib.rs::project_point`):

```
x = cos(lat) sin(lon)
y = sin(lat)
z = cos(lat) cos(lon)
```