clone

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

archive/golden-reference
atlas / README.md
README.md 12.3K rendered
1# Atlas23Atlas is a standalone, offline game-map explorer built with4[Allons](https://github.com/FelineStateMachine/allons), Wails, and5OpenLayers. The executable carries only the6application shell; each game travels as a self-contained `.atlas` bundle --7its maps, packed locations, raster tile pyramids, and category icons in one8zip archive. Drop a bundle into the application's `bundles` directory and the9game appears; drop in a newer build of the same game and it updates. There10are no sidecars, CDNs, or runtime network dependencies.1112Allons is an ordinary module dependency, fetched by version. Building13bundles additionally wants the `gamemap` corpus beside this repository:1415```text16~/Developer/17├── atlas/18└── gamemap/19```2021To hack on Allons itself, point a workspace at a local checkout rather22than editing `go.mod` — `go work init . ../allons` — since `go.work` is23ignored and a committed `replace` directive would break anyone without24the sibling checkout.2526Run Atlas from this repository:2728```sh29go generate . # once, to build the bundles into the library30go run -tags dev .31```3233Generation installs bundles straight into the central library -- `bundles/`34under the application's data directory (on macOS,35`~/Library/Application Support/dev.felinestatemachine.atlas/bundles`) -- and36the application reads the same place, so a dev run serves whatever has been37built with no pointing at anything. `ATLAS_BUNDLES_DIR` overrides the library38for either side when isolation is wanted, and `tools/generate -bundles`39writes a registry elsewhere (for an export, say) without touching the40library.4142Wails requires a desktop build tag even when it is invoked through `go run`.43Atlas supplies the additional macOS `UniformTypeIdentifiers` linker flag44itself, so no shell environment setup is needed.4546## Released builds4748Tagged releases carry portable builds for Windows (x64), macOS (Apple49Silicon), and Linux (x64); each reads `.atlas` bundles from the library50directory above. Two platform notes:5152- **macOS**: the app is unsigned. After unzipping, clear the quarantine53 with `xattr -dr com.apple.quarantine Atlas.app`, or approve it under54 System Settings → Privacy & Security → "Open Anyway".55- **Linux**: the binary links GTK 3 and WebKitGTK 4.1 at runtime56 (`libgtk-3-0`, `libwebkit2gtk-4.1-0`) — present on Ubuntu 24.04+,57 Debian 13, and recent Fedora.5859The app supports scroll/pinch zoom, drag panning, pin search, map and tile-set60selection, compact category toggles, group-level show/hide controls, floating61`display_type: "text"` labels, optional region polygons and names, and pin62details. Search and filter results list in a right-docked panel — a capped,63alphabetical shortlist of whatever survives the current filters — and opening64a location pushes the list aside for its detail card, with the way back kept65on screen. The dock folds to a thin rail and the choice is remembered with66the game. A game with one map keeps that map visible in a disabled selector.67Curated map ordering places a game's primary map first; the small ordering68table lives in `tools/generate/main.go`.6970Pins and legend entries use the archive's category SVG and resolved MapGenie71color when available, with a white exterior treatment for contrast. Normal72zoom levels use stable, priority-based decluttering. Selected and searched73locations bypass decluttering, and hovering a pin reveals its title. `Z` opts74into all visible pin labels at native-detail zoom or closer; the default75remains uncluttered. Area and region titles retain their independent toggles76and full-detail pass. Every map permits two display-only overzoom levels so77three-character geohash cells and dense annotations can be inspected without78requesting synthetic tiles. Pixel-art maps remain nearest-neighbor sharp;79photographic maps retain smooth interpolation.8081[SCRAPER_PROMPT.md](SCRAPER_PROMPT.md) contains a ready-to-use prompt for82preserving MapGenie's text-display and zone fields in the upstream archive.8384## Capture sources8586Three crawlers fill the archive, all through `tools/crawl`:8788```sh89go run ./tools/crawl -game skyrim # MapGenie90go run ./tools/crawl -ign cyberpunk-2077/night-city # IGN's native wikimaps91go run ./tools/crawl -piggyback cyberpunk-2077/night-city # Piggyback (Cyberpunk only)92```9394Each stores a content-addressed capture -- an unchanged re-crawl records no95new version -- plus the tile pyramid and the category icons. MapGenie's96icons resolve from an icon font, which the crawl now renders automatically97once a game's maps are captured (`tools/icons/render-icons.mjs`, rerunnable98by hand on a machine without node). The IGN mode refuses maps that merely99embed MapGenie, and the Piggyback importer is deliberately game-specific:100each Piggyback map projects through a transformation buried in its own101scripts, only Cyberpunk's is verified, and an unverified game is refused102rather than captured through the wrong lens.103104Sources are additive components of one game. `tools/generate` composes every105capture of a game slug into a single bundle: pins of shared ground resolve106one-to-one through an affine fitted on the places both sources name107(projections differ between sources, so the shared names -- not coordinates108-- are the basis of locality; a fit that will not close is refused), lesser109rasters ride along as aligned variants, maps no other source draws join110whole, and every map's payload carries its provenance: an origin account111naming its source, and a ledger per donor of what matched, was added,112adopted, held, or rejected. `tools/maturity` reports every build of every113game along the axes this evolution is judged by, and `cmd/cartograph`114(`go run ./cmd/cartograph`) serves a local workbench over the same115collection: builds, ledgers, diffs, and the source registry with116crawl/tiles/generate runnable from the page.117118## Building the game bundles119120With `../gamemap` beside this repository:121122```sh123go generate .124```125126`main.go` embeds only the application shell (`assets/index.html`,127`assets/app.css`, `assets/app.js`). `go generate` performs three operations:1281291. `tools/tiles` finds the highest complete source level for every configured130 layer, keeps it unstitched, and derives every missing lower level into131 `build/tiles`. Photographic source tiles retain JPEG/PNG encoding;132 pixel-art levels are normalized to lossless PNG and use nearest-neighbor133 reduction. Photo pyramids use smooth box reduction. Repeated placeholder134 borders are excluded from fit bounds.135136 Each pyramid records a stamp over the tiles it was derived from and the137 tool that derived them, so a layer nothing has changed under is left where138 it is: a run that captures one new game costs seconds rather than the half139 minute of re-deriving the rest. `-force` derives everything again.1402. `tools/generate` writes one `.atlas` bundle per game into the central141 library: the game's manifest, one payload per map, its tile pyramids, and142 its archive SVG icons, validated as each bundle is written. Maps with143 missing snapshots or incomplete configured layers are omitted. Bundles are144 named `<game>-<capture-day>-<stamp>.atlas`, versioned by the newest145 snapshot capture across the game's maps -- building the same archive146 anywhere yields the same file -- and a build already present is left147 untouched. Older versions are never pruned: the library is a registry,148 and `index.json` beside the bundles lists every game's builds, newest149 first.1503. `npm ci` and the local esbuild/OpenLayers installation produce the151 self-contained `assets/app.js` and `assets/app.css` bundles.152153Neither the binary nor a bundle has any runtime dependency on `../gamemap`,154npm, or the network.155156## Where everything lives157158Atlas keeps its game library and its build caches in fixed places:159160- **The library** — installed `.atlas` bundles, read and watched by the161 running application, written by `go generate` and by the in-app importer:162 `bundles/` under the application data directory. That is163 `~/Library/Application Support/dev.felinestatemachine.atlas` on macOS,164 `%AppData%\dev.felinestatemachine.atlas` on Windows, and165 `~/.config/dev.felinestatemachine.atlas` on Linux (`os.UserConfigDir`,166 the same convention the framework resolves). `index.json` beside the167 bundles lists every build of every game, newest first.168- **The tile cache** — `build/tiles` in this repository, ignored by git.169 `tools/tiles` derives pyramids into it and stamps each one over its170 sources, so regeneration re-derives only what changed; deleting the171 directory costs a full re-derivation from `../gamemap`.172- **Session state** — last game, camera, and panel arrangement live in the173 WebView's localStorage under the same application identity; the packaged174 data never mixes with it.175176Nothing else is cached anywhere: a bundle in the library is served straight177out of its zip, and deleting the application data directory resets Atlas178completely.179180## The .atlas format181182A `.atlas` file is a zip archive holding one game: `atlas.json` (the183manifest: game identity, version stamp, tile grid, map list), `maps/<slug>`184payloads in three parts, `tiles/<pyramid>/z/x/y` rasters stored uncompressed185for byte-range serving, and `icons/`. The format is Atlas's own and carries186nothing specific to any capture source; `internal/bundle` owns reading,187writing, and validation. Provenance still travels: each map's payload names188the source it came from and accounts for every other source folded into it.189Two bundles naming the same game slug are two versions of that game -- the190newest by capture time wins, ties broken by the build policy revision and191then the stamp, so updating a game is dropping in a newer file, and an older192file dropped beside a newer one is simply shadowed. File names carry the version for people and for cheap193existence checks; ordering always comes from the manifest inside.194195## Renderer architecture196197Atlas uses a bounded `ATLAS:PIXELS` projection over an 8192×8192 logical198extent. Each map layer is an XYZ source with a 64-tile cache, no wrapping, no199tile transitions, and interpolation selected by map type. Only visible tiles200are decoded and drawn.201202OpenLayers vector layers are ordered as raster, geohash grid, zones, zone203names, floating titles, pins, full-detail labels, and selected or searched204annotations. The grid uses a standard base32 hierarchy over the active map's205pixel bounds, capped at three characters so its terminal cells remain useful206at the available overzoom. Press `G` to toggle it, click a cell to descend, or207enter a hash in the dedicated field to jump one character at a time. `Escape`208and the back button ascend one level; escaping from the root closes the grid.209Selecting a cell promotes every enabled annotation inside it above normal210decluttering.211Ancestor sibling cells remain visible as shaded, clickable context. Together212they dim everything outside the selected cell and permit direct lateral213traversal without backing all the way out.214Map shortcuts are ignored while an input, select, textarea, or editable215control has focus.216217Named zones appear in a hierarchical table of contents above the category218legend. Click a zone to fit it in the viewport. Right-click a zone to toggle219its high-contrast outline and fill; highlighting a zone also restores the220global zone layer if it was hidden. While any zone is highlighted, unrelated221zones are shaded as context in the same visual language as geohash neighbors.222Pins outside the highlighted zone union are culled, while every enabled pin223inside that union moves to a dedicated declutter group. This shows a denser,224useful sample without reintroducing every overlapping annotation. Selected and225searched locations remain visible even when they fall outside the zone focus.226227Wheel input over the map always zooms, including immediately after using a228sidebar filter. Wheel input over the sidebar remains normal sidebar scrolling.229230All annotations and grid cells are canvas-rendered and viewport-culled; the231sidebar does not create a DOM element per location.232233Switching layers within one map replaces and clears the raster source while234retaining the current center, resolution, and overlay state.235236## Verification237238The complete local verification sequence is:239240```sh241go generate .242go test ./...243go vet ./...244go build -tags dev .245```246247The corpus-dependent tests read the central library and skip themselves when248no bundles have been built, so `go test ./...` passes on a checkout without249`../gamemap`.