clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

the included Earth volume, embedded and installed

Dami committed 0de084f
The desktop shell ships with one included volume: Earth, the committed
Blue Marble bundle at included/, an ordinary format-v3 file a release
checkout builds from completely offline. At startup the shell installs it
into the library before the host is constructed, so the first scan
already serves it, and never reads the embedded copy again -- opening,
rendering and export all go through the installed file like any imported
bundle's.

format/bundle grows the one seam this needs: InstallBytes, the in-memory
door beside Install, keeping every promise the path door makes --
validated before anything is written, staged under a name a concurrent
scan passes over, idempotent when the build is already present, and never
over another build, because builds sit side by side and the fold decides.
A built-in that cannot be installed is a startup error rather than a
silent absence.

The root package's tests hold the committed artifact to what it is
specified to be -- the manifest, the whole-sphere declarations, the
stored tiles of every level, offline purity, the NASA credit in the
ledger -- and walk the startup order over an empty temporary library.
Decision 19 carries the reasoning, and included/README.md the provenance
and the recipe.
commit
0de084f820b81d9a8477e2418a55bb091d3dc1a6
parent
99549cf50fd53968687943edf8cdf1659b147f03
author
Dami <dami.etoile@gmail.com>
kind
feat risk score 1.4 of 3, Jev's estimate
10 files changed, +780 -1
MREADME.md6+
Mdocs/app.md17+-
Adocs/decisions/0019-an-earth-volume-is-included.md55++++
Mdocs/decisions/README.md1+
Mformat/bundle/registry_test.go92++++++
Mformat/bundle/scan.go56++++
Aincluded/README.md63++++
Aincluded/earth-20260803-eaf23347cf1e.atlasbin
Mmain.go48+++
Amain_test.go443++++++++++++++++++++++++++++++
M README.md +6 -0
M docs/app.md +16 -1
@@ -111,7 +111,7 @@ wails.Run(&options.App{
111111 })112112 ```113113 114-Four things about it are worth knowing.114+Five things about it are worth knowing.115115 116116 **Where the library is.** `$ATLAS_DATA_DIR`, else `os.UserConfigDir()` +117117 `dev.felinestatemachine.atlas` — the identifier existing libraries already
@@ -121,6 +121,21 @@ moves the library alone, which is what a development run points at a freshly
121121 composed `dist/bundles`. The headless host reads the same two variables122122 (`cmd/atlas/serve.go`).123123 124+**The included Earth volume.** The shell embeds the committed bundle at125+`included/` (`//go:embed included/*.atlas`) and installs it into the library126+before the host is constructed, so the first scan already serves Earth and a127+first launch opens onto a world instead of the empty-library card. The install128+goes through `format/bundle`'s own path — validated, versioned, staged,129+idempotent — so a second launch is a no-op and another Earth build in the130+library stays beside it for the fold to order; a built-in that cannot be131+installed is a startup error, never a silent absence. After installation the132+embedded copy is done with: everything reads the installed file, exactly as if133+the reader had imported it134+([decision 19](decisions/0019-an-earth-volume-is-included.md);135+`included/README.md` carries the provenance and the recipe). The headless host136+installs nothing: `atlas serve` uses exactly the registry the operator gives137+it.138+124139 **Where the seam is.** `//go:embed static` — the root `static/` directory,125140 holding the seam's built bundle as `app.js`. `make static` puts the same bytes126141 in `dist/static`, which is what a `-static` mount is pointed at; the shell
A docs/decisions/0019-an-earth-volume-is-included.md +55 -0
@@ -0,0 +1,55 @@
1+# 19. The desktop ships with an included Earth volume2+3+- **Date:** 2026-08-034+- **Status:** accepted5+- **Where it is written down:** [app.md](../app.md) §1.4; [generate.md](../generate.md) §2.4; `included/README.md`; `main.go`; `internal/generate/crawl/bluemarble.go`; `internal/generate/sources/nasabluemarble`6+7+## Context8+9+A fresh install opened onto the empty-library card. Everything past that card10+assumed the reader could produce or obtain a `.atlas` file, which meant the11+whole pipeline stood between a new reader and their first world — and there was12+no committed bundle with real rasters anywhere in the repository, so nothing13+end to end ever proved the imagery leg of the data plane.14+15+NASA Earth Observatory publishes Blue Marble Next Generation: a global,16+cloud-free, true-color composite of the whole planet, free of copyright, at a17+size that survives a repository. The format already had everything needed to18+carry it — the whole-sphere equirectangular declarations NASA Trek's worlds19+make, a registry fold that lets builds sit side by side, and an install path20+with validation, versioned naming, atomic staging and idempotency.21+22+## Decision23+24+Ship the desktop application with one included volume: Earth, as a real,25+ordinary format-v3 bundle composed from a pinned Blue Marble capture, committed26+at `included/` and embedded in the desktop executable. At startup the shell27+installs it into the library through `format/bundle`'s own rules, before the28+host's first scan, and never touches the embedded copy again.29+30+The capture is pinned — one URL, one SHA-256, one recorded capture time, one31+spelled derivation policy — and the crawl that fetches it is the only networked32+step, run by hand (`make included-earth`). A digest mismatch is a refusal.33+Ordinary builds, tests and every launch are offline; a release checkout builds34+from the committed artifact alone.35+36+The included build is ordinary on purpose: nothing anywhere special-cases it.37+The application handler does not know built-in volumes exist; `atlas serve` and38+the CLI install nothing; a newer Earth build installed later simply wins the39+fold, and deleting the installed file brings it back at next launch by the same40+idempotent install.41+42+## Consequences43+44+- A first launch opens onto a world, and the empty-library card becomes a45+ state most readers never see.46+- The repository carries a ~5 MiB binary artifact, regenerable byte for byte47+ from the pin by `make included-earth`, with its provenance and recipe beside48+ it in `included/README.md`.49+- The e2e registry gains its first volume with real rasters, so the data50+ plane's imagery leg is proven in a browser rather than only inferred.51+- The embedded copy costs every desktop binary its size once; the headless52+ host and CLI cost nothing.53+- Anything that later enriches Earth — features, layers, regional extraction —54+ arrives as ordinary builds and ordinary enrichment, because the included55+ volume made no special claims for itself.
M docs/decisions/README.md +1 -0
M format/bundle/registry_test.go +92 -0
@@ -313,3 +313,95 @@ func TestInstallRefusesABrokenBundle(t *testing.T) {
313313 t.Errorf("a refused install left %d files behind", len(entries))314314 }315315 }316+317+// TestInstallBytesKeepsInstallsPromises: the in-memory door -- an embedded318+// asset, a download -- keeps every promise the path door makes. A valid bundle319+// lands under its versioned name; the same bytes again are a no-op that adds320+// no second file; a distinct build of the same volume lands beside the first321+// rather than over it; and broken bytes are refused before anything is322+// written.323+func TestInstallBytesKeepsInstallsPromises(t *testing.T) {324+ library := t.TempDir()325+ staging := t.TempDir()326+ source := fixture{slug: "earth", title: "Earth", createdAt: "2026-08-03T14:30:39Z", stamp: "aa"}.build(t, staging)327+ data, err := os.ReadFile(source)328+ if err != nil {329+ t.Fatal(err)330+ }331+332+ first, err := bundle.InstallBytes(library, data)333+ if err != nil {334+ t.Fatal(err)335+ }336+ if want := "earth-20260803-aa.atlas"; filepath.Base(first.Locator) != want {337+ t.Errorf("installed as %s, want %s", filepath.Base(first.Locator), want)338+ }339+ firstInfo, err := os.Stat(first.Locator)340+ if err != nil {341+ t.Fatal(err)342+ }343+344+ again, err := bundle.InstallBytes(library, data)345+ if err != nil {346+ t.Fatal(err)347+ }348+ if again.Locator != first.Locator {349+ t.Errorf("a second install landed at %s", again.Locator)350+ }351+ afterInfo, err := os.Stat(first.Locator)352+ if err != nil {353+ t.Fatal(err)354+ }355+ if !afterInfo.ModTime().Equal(firstInfo.ModTime()) || afterInfo.Size() != firstInfo.Size() {356+ t.Error("a no-op install touched the installed file")357+ }358+359+ other := fixture{slug: "earth", title: "Earth", createdAt: "2026-08-02T00:00:00Z", stamp: "bb"}.build(t, staging)360+ otherData, err := os.ReadFile(other)361+ if err != nil {362+ t.Fatal(err)363+ }364+ beside, err := bundle.InstallBytes(library, otherData)365+ if err != nil {366+ t.Fatal(err)367+ }368+ if beside.Locator == first.Locator {369+ t.Error("a distinct build overwrote the installed one")370+ }371+ entries, err := os.ReadDir(library)372+ if err != nil {373+ t.Fatal(err)374+ }375+ if len(entries) != 2 {376+ t.Errorf("the library holds %d files; two builds sit side by side", len(entries))377+ }378+ if held, err := os.ReadFile(first.Locator); err != nil || len(held) != int(firstInfo.Size()) {379+ t.Errorf("the first build did not survive the second: %v", err)380+ }381+}382+383+func TestInstallBytesRefusesBrokenBytes(t *testing.T) {384+ library := t.TempDir()385+ if _, err := bundle.InstallBytes(library, []byte("not a bundle")); err == nil {386+ t.Error("broken bytes were let in")387+ }388+ staging := t.TempDir()389+ broken := fixture{390+ slug: "earth", title: "Earth",391+ worlds: []fixtureWorld{{slug: "overworld", points: 7, countsStated: true}},392+ }.build(t, staging)393+ data, err := os.ReadFile(broken)394+ if err != nil {395+ t.Fatal(err)396+ }397+ if _, err := bundle.InstallBytes(library, data); err == nil {398+ t.Error("an invalid bundle was let in")399+ }400+ entries, err := os.ReadDir(library)401+ if err != nil {402+ t.Fatal(err)403+ }404+ if len(entries) != 0 {405+ t.Errorf("a refused install left %d files behind", len(entries))406+ }407+}
M format/bundle/scan.go +56 -0
@@ -1,6 +1,7 @@
11 package bundle22 33 import (4+ "bytes"45 "fmt"56 "io"67 "os"
@@ -125,6 +126,61 @@ func Install(dir, source string) (Descriptor, error) {
125126 return descriptor, nil126127 }127128 129+// InstallBytes installs a bundle held in memory -- an embedded asset, a130+// download -- into dir under its versioned name, and reports the descriptor of131+// the installed build. It keeps every promise [Install] makes: the bytes are132+// validated before anything is written, the copy stages under a temporary name133+// a concurrent scan passes over, an already-present build is a no-op, and the134+// installed file is reopened and validated because the copy is what will serve.135+func InstallBytes(dir string, source []byte) (Descriptor, error) {136+ opened, err := NewReader(bytes.NewReader(source), int64(len(source)))137+ if err != nil {138+ return Descriptor{}, err139+ }140+ manifest := opened.Manifest141+ if err := opened.Validate(); err != nil {142+ return Descriptor{}, err143+ }144+145+ target := filepath.Join(dir, VersionedFileName(manifest))146+ if _, err := os.Stat(target); err == nil {147+ // This exact build is already installed under its own name.148+ return Describe(target)149+ }150+ staged, err := os.CreateTemp(dir, ".installing-*")151+ if err != nil {152+ return Descriptor{}, fmt.Errorf("stage %s: %w", filepath.Base(target), err)153+ }154+ defer os.Remove(staged.Name())155+ if _, err := staged.Write(source); err != nil {156+ staged.Close()157+ return Descriptor{}, fmt.Errorf("stage %s: %w", filepath.Base(target), err)158+ }159+ if err := staged.Close(); err != nil {160+ return Descriptor{}, fmt.Errorf("stage %s: %w", filepath.Base(target), err)161+ }162+ // CreateTemp opens private files; a bundle is an artifact anyone may copy.163+ if err := os.Chmod(staged.Name(), 0o644); err != nil {164+ return Descriptor{}, fmt.Errorf("stage %s: %w", filepath.Base(target), err)165+ }166+ if err := os.Rename(staged.Name(), target); err != nil {167+ return Descriptor{}, fmt.Errorf("install %s: %w", filepath.Base(target), err)168+ }169+ descriptor, err := Describe(target)170+ if err != nil {171+ return Descriptor{}, fmt.Errorf("installed %s: %w", filepath.Base(target), err)172+ }173+ installed, err := Open(target)174+ if err != nil {175+ return Descriptor{}, fmt.Errorf("installed %s: %w", filepath.Base(target), err)176+ }177+ defer installed.Close()178+ if err := installed.Validate(); err != nil {179+ return Descriptor{}, fmt.Errorf("installed %s: %w", filepath.Base(target), err)180+ }181+ return descriptor, nil182+}183+128184 // copyInto stages a copy of source beside target and renames it into place.129185 func copyInto(dir, source, target string) error {130186 from, err := os.Open(source)
A included/README.md +63 -0
@@ -0,0 +1,63 @@
1+# The included Earth volume2+3+The one `.atlas` file in this directory is the included Earth volume: a real,4+ordinary format-v3 bundle the desktop shell embeds (`//go:embed included/*.atlas`5+in `main.go`) and installs into the reader's library at startup, through the6+same `format/bundle` path an imported bundle takes. A first launch therefore7+opens onto a world instead of an empty library, and everything after8+installation — opening, rendering, analysis, export, registry ordering — reads9+the installed file, never the embedded copy. `atlas serve` and the CLI embed10+nothing: they use exactly the registry the operator gives them.11+12+The volume is raster-only: one world (`earth`), one lens (`Blue Marble`), no13+collections, no features, and no runtime network dependency of any kind.14+15+## Provenance16+17+| Fact | Value |18+|---|---|19+| Product | Blue Marble: Next Generation, July 2004 — NASA Earth Observatory's global, cloud-free, true-color base map |20+| Product page | <https://science.nasa.gov/earth/earth-observatory/blue-marble-next-generation/base-map/> |21+| Source image | <https://assets.science.nasa.gov/content/dam/science/esd/eo/images/bmng/bmng-base/july/world.200407.3x21600x10800.jpg> |22+| Source dimensions | 21600 × 10800 |23+| Source SHA-256 | `dea8b4dc8a4f93f5f8bce0c8c85a508a178e7901e9ed8e6bf86e6ce7ef6d61e2` |24+| First captured | `2026-08-03T14:30:39Z` |25+| Credit | NASA Earth Observatory |26+27+NASA imagery is free of copyright. NASA's media guidelines ask that the credit28+be preserved, that no NASA logo be used, and that no NASA endorsement be29+implied; the bundle's provenance ledger carries the credit, and nothing here30+does the other two. No source URL appears inside the bundle — format v3's31+runtime-URL prohibition holds for this volume exactly as for any other.32+33+## Derivation34+35+The pinned source is downsampled to the 8192 × 4096 reference level — the top36+half of the world square, the shared whole-sphere equirectangular window37+(`atlas.geometry.equirect.px = 0,0,8192,4096`,38+`atlas.geometry.equirect.deg = -180,90,180,-90`) — through the deterministic39+fixed-point Catmull-Rom resampler in `internal/generate/crawl/resample.go`40+(`catmull-rom-fixed15`), cut into 256-pixel tiles, and encoded as JPEG at41+quality **90** (`internal/generate/crawl/bluemarble.go` is the one place that42+setting is spelled). The ordinary tile lane derives every shallower level,43+coverage, bounds and stamps from that reference level, and the ordinary compose44+lane writes and validates the bundle. The full extent survives uncropped, and45+tiles are stored, not deflated, as format v3 requires.46+47+## Regeneration48+49+```sh50+make included-earth51+```52+53+The first step (`atlas crawl -source nasa-blue-marble earth`) is the only54+networked part: it downloads the source image once into the staged archive at55+`crawl/blue-marble/fmg-archive`, refuses any digest but the pinned one, and56+fetches nothing at all when the source is already there. The remaining steps57+derive and compose offline. The same pinned source, capture metadata,58+generation policy and tool version produce this file byte for byte, under this59+same name — a rebuild of an unchanged archive writes nothing.60+61+Ordinary builds, tests, application startup and first launch never contact62+NASA: a release checkout builds from this committed artifact completely63+offline.
A included/earth-20260803-eaf23347cf1e.atlas +0 -0

Binary file.

M main.go +48 -0
@@ -36,6 +36,7 @@ import (
3636 "github.com/wailsapp/wails/v2/pkg/options"3737 "github.com/wailsapp/wails/v2/pkg/options/assetserver"3838 39+ "github.com/FelineStateMachine/atlas/format/bundle"3940 "github.com/FelineStateMachine/atlas/internal/app"4041 "github.com/FelineStateMachine/atlas/internal/app/hostenv/oshost"4142 "github.com/FelineStateMachine/atlas/internal/app/hostenv/wailshost"
@@ -57,6 +58,17 @@ import (
5758 //go:embed static5859 var seam embed.FS5960 61+// The included Earth volume: a real, ordinary .atlas bundle -- NASA's Blue62+// Marble base map, composed by `make included-earth` and committed at63+// included/ -- embedded so a first launch opens onto a world instead of an64+// empty library. It is embedded here and only here: the desktop shell is the65+// host that owns a library, where `atlas serve` and the CLI use exactly the66+// registry the operator gives them. included/README.md is the provenance and67+// the regeneration recipe.68+//69+//go:embed included/*.atlas70+var included embed.FS71+6072 // appIdentifier is the directory the application keeps its data under, on6173 // whichever configuration directory the machine calls its own. It is the6274 // identifier the pre-rewrite shell used, unchanged and on purpose: a reader's
@@ -99,6 +111,17 @@ func run() error {
99111 if err := os.MkdirAll(library, 0o755); err != nil {100112 return fmt.Errorf("creating the library at %s: %w", library, err)101113 }114+ // The included Earth build lands before the host is constructed, so the115+ // host's first scan of the library already sees it. Installation goes116+ // through format/bundle's own path -- validated, versioned, staged,117+ // idempotent -- so an already-installed build is a no-op and another Earth118+ // build in the library is left exactly where it is, side by side, for the119+ // registry fold to order. A built-in that cannot be installed is a startup120+ // error rather than a silent absence: the application would be shipping a121+ // broken asset, and that has to be heard about.122+ if err := installIncluded(library); err != nil {123+ return fmt.Errorf("installing the included Earth volume: %w", err)124+ }102125 103126 // The window is declared before the host because the host holds its104127 // picker and the window learns its context at startup, after the handler
@@ -144,6 +167,31 @@ func run() error {
144167 })145168 }146169 170+// installIncluded installs every embedded bundle into the library. After it171+// returns, the embedded copies are done with: opening, rendering, analysis and172+// export all read the installed files through the same paths any imported173+// bundle is read through.174+func installIncluded(library string) error {175+ names, err := fs.Glob(included, "included/*.atlas")176+ if err != nil {177+ return err178+ }179+ for _, name := range names {180+ data, err := included.ReadFile(name)181+ if err != nil {182+ return fmt.Errorf("%s: %w", name, err)183+ }184+ installed, err := bundle.InstallBytes(library, data)185+ if err != nil {186+ return fmt.Errorf("%s: %w", name, err)187+ }188+ slog.Info("included volume installed", logging.Op("desktop"),189+ logging.Volume(installed.Slug), logging.Stamp(bundle.ShortStamp(installed.Stamp)),190+ logging.Path(installed.Locator))191+ }192+ return nil193+}194+147195 // dataDir is where the application keeps what is its own: the library of148196 // installed volumes and the session records that remember where a reader was.149197 // ATLAS_DATA_DIR points the whole of it somewhere else, which is how a
A main_test.go +443 -0
@@ -0,0 +1,443 @@
1+package main2+3+// The included Earth volume, held to its promises: the committed bundle is a4+// valid, ordinary format-v3 volume; startup installs it through the format's5+// own registry rules; and the installed file serves through the same6+// application paths any imported bundle serves through. Every test here reads7+// the embedded asset -- the same bytes a release build ships -- so what is8+// judged is what a first launch actually opens.9+10+import (11+ "bytes"12+ "encoding/json"13+ "image/jpeg"14+ "io"15+ "io/fs"16+ "net/http"17+ "net/http/httptest"18+ "os"19+ "path/filepath"20+ "strings"21+ "testing"22+23+ "github.com/FelineStateMachine/atlas/format/bundle"24+ "github.com/FelineStateMachine/atlas/format/semconv"25+ "github.com/FelineStateMachine/atlas/internal/app"26+ "github.com/FelineStateMachine/atlas/internal/app/hostenv/oshost"27+)28+29+// includedEarth reads the one embedded bundle. There being exactly one is part30+// of the contract: the embed pattern would happily carry a stray second build,31+// and two included Earths would be two answers to what a first launch shows.32+func includedEarth(t *testing.T) []byte {33+ t.Helper()34+ names, err := fs.Glob(included, "included/*.atlas")35+ if err != nil {36+ t.Fatal(err)37+ }38+ if len(names) != 1 {39+ t.Fatalf("the executable embeds %d bundles %v, want exactly the one Earth", len(names), names)40+ }41+ data, err := included.ReadFile(names[0])42+ if err != nil {43+ t.Fatal(err)44+ }45+ return data46+}47+48+func openIncluded(t *testing.T) *bundle.Reader {49+ t.Helper()50+ data := includedEarth(t)51+ reader, err := bundle.NewReader(bytes.NewReader(data), int64(len(data)))52+ if err != nil {53+ t.Fatal(err)54+ }55+ return reader56+}57+58+// earthPayload is as much of the world payload as these tests read.59+type earthPayload struct {60+ Lenses []struct {61+ Name string `json:"name"`62+ Tiles string `json:"tiles"`63+ MinZoom int `json:"minZoom"`64+ MaxZoom int `json:"maxZoom"`65+ FullZoom int `json:"fullZoom"`66+ SourceZoom int `json:"sourceZoom"`67+ Formats []string `json:"formats"`68+ Bounds *struct {69+ X, Y, Width, Height int70+ } `json:"bounds"`71+ } `json:"lenses"`72+ Collections []json.RawMessage `json:"collections"`73+ Attrs map[string]string `json:"attrs"`74+ Merged []struct {75+ Source string `json:"source"`76+ Slug string `json:"slug"`77+ Origin bool `json:"origin"`78+ } `json:"merged"`79+}80+81+func readEarthPayload(t *testing.T, reader *bundle.Reader) earthPayload {82+ t.Helper()83+ data, err := reader.ReadEntry(bundle.WorldEntryName("earth", bundle.WorldSuffix))84+ if err != nil {85+ t.Fatal(err)86+ }87+ var payload earthPayload88+ if err := json.Unmarshal(data, &payload); err != nil {89+ t.Fatal(err)90+ }91+ return payload92+}93+94+// TestIncludedEarthIsAValidOrdinaryBundle: the committed built-in passes the95+// format's whole validation surface and says exactly what the included volume96+// is specified to say -- format 3, conventions 2, volume earth, one world97+// earth, raster-only.98+func TestIncludedEarthIsAValidOrdinaryBundle(t *testing.T) {99+ reader := openIncluded(t)100+ if err := reader.Validate(); err != nil {101+ t.Fatalf("the committed built-in does not validate: %v", err)102+ }103+ m := reader.Manifest104+ if m.FormatVersion != 3 || m.Conventions != 2 {105+ t.Errorf("format %d conventions %d, want 3 and 2", m.FormatVersion, m.Conventions)106+ }107+ if m.Volume.Slug != "earth" || m.Volume.Title != "Earth" {108+ t.Errorf("volume %s/%s", m.Volume.Slug, m.Volume.Title)109+ }110+ if len(m.Worlds) != 1 || m.Worlds[0].Slug != "earth" || m.Worlds[0].Title != "Earth" {111+ t.Fatalf("worlds %+v, want exactly the one earth", m.Worlds)112+ }113+ if m.Worlds[0].Points != 0 || m.Worlds[0].Paths != 0 || m.Worlds[0].Areas != 0 {114+ t.Errorf("the manifest counts features on a raster-only world: %+v", m.Worlds[0])115+ }116+ if m.Version.CreatedAt != "2026-08-03T14:30:39Z" {117+ t.Errorf("createdAt %s is not the pinned capture time", m.Version.CreatedAt)118+ }119+}120+121+// TestIncludedEarthDeclaresTheWholeSphere: one Blue Marble lens over the122+// complete 8192×4096 Earth surface, no collections, and exactly the spherical123+// equirectangular declarations the analysis contracts recognise.124+func TestIncludedEarthDeclaresTheWholeSphere(t *testing.T) {125+ reader := openIncluded(t)126+ payload := readEarthPayload(t, reader)127+128+ if len(payload.Collections) != 0 {129+ t.Errorf("%d collections; the included volume is raster-only", len(payload.Collections))130+ }131+ if len(payload.Lenses) != 1 {132+ t.Fatalf("%d lenses, want the one Blue Marble", len(payload.Lenses))133+ }134+ lens := payload.Lenses[0]135+ if lens.Name != "Blue Marble" {136+ t.Errorf("lens %q", lens.Name)137+ }138+ if lens.MinZoom != 0 || lens.MaxZoom != 5 || lens.FullZoom != 5 || lens.SourceZoom != 5 {139+ t.Errorf("zooms min %d max %d full %d source %d, want 0/5/5/5",140+ lens.MinZoom, lens.MaxZoom, lens.FullZoom, lens.SourceZoom)141+ }142+ if len(lens.Formats) != 6 {143+ t.Errorf("%d formats for 6 levels", len(lens.Formats))144+ }145+ for _, format := range lens.Formats {146+ if format != "jpg" {147+ t.Errorf("a level is %q, want jpg throughout", format)148+ }149+ }150+ if lens.Bounds == nil || *lens.Bounds != struct{ X, Y, Width, Height int }{0, 0, 8192, 4096} {151+ t.Errorf("bounds %+v, want the complete 0,0,8192,4096 surface", lens.Bounds)152+ }153+154+ want := map[string]string{155+ semconv.KeyGeometrySurface: "sphere",156+ semconv.KeyGeometryProjection: "equirect",157+ semconv.KeyGeometryEquirectPx: "0,0,8192,4096",158+ semconv.KeyGeometryEquirectDeg: "-180,90,180,-90",159+ semconv.KeyGeometryBody: "earth",160+ semconv.KeyGeometryRadiusKM: "6371.0088",161+ }162+ if len(payload.Attrs) != len(want) {163+ t.Errorf("the world carries %d attributes %v, want %d", len(payload.Attrs), payload.Attrs, len(want))164+ }165+ for key, value := range want {166+ if payload.Attrs[key] != value {167+ t.Errorf("world says %s=%q, want %q", key, payload.Attrs[key], value)168+ }169+ }170+171+ if len(payload.Merged) != 1 || !payload.Merged[0].Origin ||172+ payload.Merged[0].Source != "NASA Earth Observatory" ||173+ payload.Merged[0].Slug != "nasa-blue-marble" {174+ t.Errorf("provenance ledger %+v; the credit is part of the artifact", payload.Merged)175+ }176+}177+178+// TestIncludedEarthPyramidOpensAtEveryLevel: every advertised level holds its179+// whole half-height window of stored, decodable JPEG tiles.180+func TestIncludedEarthPyramidOpensAtEveryLevel(t *testing.T) {181+ reader := openIncluded(t)182+ perLevel := map[string]int{}183+ for _, name := range reader.Names() {184+ if !strings.HasPrefix(name, "tiles/earth/") {185+ continue186+ }187+ parts := strings.Split(name, "/")188+ perLevel[parts[2]]++189+ if !reader.Stored(name) {190+ t.Fatalf("%s is compressed; format v3 stores tiles", name)191+ }192+ }193+ wantPerLevel := map[string]int{"0": 1, "1": 2, "2": 8, "3": 32, "4": 128, "5": 512}194+ for level, want := range wantPerLevel {195+ if perLevel[level] != want {196+ t.Errorf("level %s holds %d tiles, want %d", level, perLevel[level], want)197+ }198+ }199+ // One tile of every level decodes as the JPEG its name claims.200+ for _, name := range []string{201+ "tiles/earth/0/0/0.jpg", "tiles/earth/1/1/0.jpg", "tiles/earth/2/3/1.jpg",202+ "tiles/earth/3/7/3.jpg", "tiles/earth/4/15/7.jpg", "tiles/earth/5/31/15.jpg",203+ } {204+ data, err := reader.ReadEntry(name)205+ if err != nil {206+ t.Fatalf("%s: %v", name, err)207+ }208+ if _, err := jpeg.Decode(bytes.NewReader(data)); err != nil {209+ t.Fatalf("%s does not decode: %v", name, err)210+ }211+ }212+ if !reader.Stored(bundle.WorldEntryName("earth", bundle.PackedSuffix)) {213+ t.Error("the packed payload is compressed; format v3 stores it")214+ }215+}216+217+// TestIncludedEarthCarriesNoRuntimeURL is offline purity, asserted directly on218+// the shipped bytes over and above Validate's own scan.219+func TestIncludedEarthCarriesNoRuntimeURL(t *testing.T) {220+ reader := openIncluded(t)221+ for _, name := range []string{222+ bundle.WorldEntryName("earth", bundle.WorldSuffix),223+ bundle.WorldEntryName("earth", bundle.TextSuffix),224+ } {225+ data, err := reader.ReadEntry(name)226+ if err != nil {227+ t.Fatal(err)228+ }229+ for _, scheme := range []string{"http://", "https://"} {230+ if bytes.Contains(data, []byte(scheme)) {231+ t.Errorf("%s carries %q", name, scheme)232+ }233+ }234+ }235+}236+237+// TestInstallIncludedIsIdempotentAndOverwritesNothing: a first launch installs238+// one versioned Earth file; a second launch changes nothing; and a distinct239+// Earth build already in the library stays exactly where it is, side by side.240+func TestInstallIncludedIsIdempotentAndOverwritesNothing(t *testing.T) {241+ library := t.TempDir()242+ if err := installIncluded(library); err != nil {243+ t.Fatal(err)244+ }245+ entries, err := os.ReadDir(library)246+ if err != nil {247+ t.Fatal(err)248+ }249+ if len(entries) != 1 {250+ t.Fatalf("a first launch installed %d files", len(entries))251+ }252+ installed := filepath.Join(library, entries[0].Name())253+ if !strings.HasPrefix(entries[0].Name(), "earth-20260803-") || !strings.HasSuffix(entries[0].Name(), ".atlas") {254+ t.Errorf("installed as %s, want the versioned earth name", entries[0].Name())255+ }256+ if _, err := bundle.Describe(installed); err != nil {257+ t.Fatalf("the installed file does not describe: %v", err)258+ }259+ before, err := os.Stat(installed)260+ if err != nil {261+ t.Fatal(err)262+ }263+264+ if err := installIncluded(library); err != nil {265+ t.Fatal(err)266+ }267+ entries, err = os.ReadDir(library)268+ if err != nil {269+ t.Fatal(err)270+ }271+ if len(entries) != 1 {272+ t.Fatalf("a second launch grew the library to %d files", len(entries))273+ }274+ after, err := os.Stat(installed)275+ if err != nil {276+ t.Fatal(err)277+ }278+ if !after.ModTime().Equal(before.ModTime()) || after.Size() != before.Size() {279+ t.Error("a second launch touched the installed file")280+ }281+282+ // A distinct Earth build -- another capture day, another stamp -- is not283+ // overwritten and not deleted; the registry fold decides which one serves.284+ other := writeDistinctEarth(t, library)285+ if err := installIncluded(library); err != nil {286+ t.Fatal(err)287+ }288+ if _, err := os.Stat(other); err != nil {289+ t.Errorf("a launch removed the other Earth build: %v", err)290+ }291+ entries, err = os.ReadDir(library)292+ if err != nil {293+ t.Fatal(err)294+ }295+ if len(entries) != 2 {296+ t.Errorf("the library holds %d files; two Earth builds sit side by side", len(entries))297+ }298+}299+300+// writeDistinctEarth states a second, minimal Earth build through the format's301+// real writer, dated after the included one so the fold prefers it.302+func writeDistinctEarth(t *testing.T, dir string) string {303+ t.Helper()304+ manifest := bundle.Manifest{305+ Format: bundle.Format,306+ FormatVersion: bundle.FormatVersion,307+ Volume: bundle.Volume{Slug: "earth", Title: "Earth"},308+ Version: bundle.Version{Stamp: "ffff", CreatedAt: "2026-09-01T00:00:00Z"},309+ TileGrid: bundle.TileGrid{SourceZoom: 13, FirstTile: 4064, TileSize: 256, Size: 8192},310+ Worlds: []bundle.WorldEntry{{311+ Slug: "flat", Title: "Flat", UpdatedAt: "2026-09-01T00:00:00Z",312+ }},313+ }314+ path := filepath.Join(dir, bundle.VersionedFileName(manifest))315+ file, err := os.Create(path)316+ if err != nil {317+ t.Fatal(err)318+ }319+ writer, err := bundle.NewWriter(file, manifest)320+ if err != nil {321+ t.Fatal(err)322+ }323+ if err := writer.AddDeflated("worlds/flat.json",324+ []byte(`{"lenses":[{"name":"Flat","tiles":"flat","minZoom":0,"maxZoom":0,"fullZoom":0,"sourceZoom":0,"formats":["jpg"],"interpolate":true}],"collections":[]}`)); err != nil {325+ t.Fatal(err)326+ }327+ if err := writer.AddStored("worlds/flat.bin", bytes.NewReader(bundle.PackLocations(nil))); err != nil {328+ t.Fatal(err)329+ }330+ if err := writer.AddDeflated("worlds/flat.text", []byte(`{}`)); err != nil {331+ t.Fatal(err)332+ }333+ if err := writer.AddStored("tiles/flat/0/0/0.jpg", bytes.NewReader(statedJPEG(t))); err != nil {334+ t.Fatal(err)335+ }336+ if err := writer.Close(); err != nil {337+ t.Fatal(err)338+ }339+ if err := file.Close(); err != nil {340+ t.Fatal(err)341+ }342+ return path343+}344+345+func statedJPEG(t *testing.T) []byte {346+ t.Helper()347+ reader := openIncluded(t)348+ data, err := reader.ReadEntry("tiles/earth/0/0/0.jpg")349+ if err != nil {350+ t.Fatal(err)351+ }352+ return data353+}354+355+// TestDesktopStartupExposesEarthOnTheFirstScan walks the shell's own startup356+// order over a temporary empty data directory: library created, built-in357+// installed, host constructed -- and the host's first scan already serves358+// Earth, with no network anywhere in the path.359+func TestDesktopStartupExposesEarthOnTheFirstScan(t *testing.T) {360+ data := t.TempDir()361+ library := filepath.Join(data, "bundles")362+ if err := os.MkdirAll(library, 0o755); err != nil {363+ t.Fatal(err)364+ }365+ if err := installIncluded(library); err != nil {366+ t.Fatal(err)367+ }368+ host, err := oshost.New(oshost.Options{369+ BundlesDir: library,370+ SessionsDir: filepath.Join(data, "sessions"),371+ })372+ if err != nil {373+ t.Fatal(err)374+ }375+ t.Cleanup(func() { host.Volumes().(interface{ Close() error }).Close() })376+377+ volumes := host.Volumes().Volumes()378+ if len(volumes) != 1 {379+ t.Fatalf("the first scan lists %d volumes", len(volumes))380+ }381+ manifest := volumes[0].Manifest()382+ if manifest.Volume.Slug != "earth" || manifest.Volume.Title != "Earth" {383+ t.Errorf("the first scan serves %s/%s", manifest.Volume.Slug, manifest.Volume.Title)384+ }385+}386+387+// TestEarthServesThroughTheApplication: the installed file -- never the388+// embedded copy -- answers the application's own data plane: the catalog lists389+// Earth, the world page opens, and a raster tile reads back as the JPEG it is.390+func TestEarthServesThroughTheApplication(t *testing.T) {391+ data := t.TempDir()392+ library := filepath.Join(data, "bundles")393+ if err := os.MkdirAll(library, 0o755); err != nil {394+ t.Fatal(err)395+ }396+ if err := installIncluded(library); err != nil {397+ t.Fatal(err)398+ }399+ host, err := oshost.New(oshost.Options{400+ BundlesDir: library,401+ SessionsDir: filepath.Join(data, "sessions"),402+ })403+ if err != nil {404+ t.Fatal(err)405+ }406+ t.Cleanup(func() { host.Volumes().(interface{ Close() error }).Close() })407+ handler := app.New(host, app.Options{})408+ server := httptest.NewServer(handler)409+ defer server.Close()410+411+ catalog := get(t, server.Client(), server.URL+"/data/catalog.json")412+ if !strings.Contains(string(catalog), `"earth"`) {413+ t.Errorf("the catalog does not list earth: %s", catalog)414+ }415+416+ page := get(t, server.Client(), server.URL+"/v/earth/earth")417+ if !strings.Contains(string(page), "Earth") {418+ t.Error("the world page does not open on Earth")419+ }420+421+ stamp := bundle.ShortStamp(host.Volumes().Volumes()[0].Manifest().Version.Stamp)422+ tile := get(t, server.Client(), server.URL+"/data/v/earth/"+stamp+"/tiles/earth/0/0/0.jpg")423+ if _, err := jpeg.Decode(bytes.NewReader(tile)); err != nil {424+ t.Fatalf("the served tile does not decode: %v", err)425+ }426+}427+428+func get(t *testing.T, client *http.Client, url string) []byte {429+ t.Helper()430+ response, err := client.Get(url)431+ if err != nil {432+ t.Fatal(err)433+ }434+ defer response.Body.Close()435+ if response.StatusCode != http.StatusOK {436+ t.Fatalf("GET %s answered %d", url, response.StatusCode)437+ }438+ body, err := io.ReadAll(response.Body)439+ if err != nil {440+ t.Fatal(err)441+ }442+ return body443+}