- Phase 1: POI database (11,309 locations, SQLite with spatial queries) - Phase 2: Tile decoder + Leaflet.js web viewer (3 map layers, search, categories) - Phase 3: .db/.fch parser WIP for fog-of-war overlay - Full README with roadmap, architecture, and usage docs
287 lines
10 KiB
Markdown
287 lines
10 KiB
Markdown
# Nerdhalla Valheim World Map
|
||
|
||
> **Seed:** `yzZ5fr2tGa` | **World:** 24000 × 24000 | **Biomes:** 9 | **POIs:** 11,309
|
||
|
||
An interactive web map of the Nerdhalla Valheim world, built from a `valheim-map.world` export. Includes biome/height map tiles, a searchable POI database, and a roadmap for fog-of-war integration.
|
||
|
||
---
|
||
|
||
## Quick Start
|
||
|
||
```bash
|
||
# Serve the map viewer
|
||
cd ~/projects/valheim-map/output && python3 -m http.server 8765
|
||
# Open: http://72.60.69.120:8765/index.html
|
||
```
|
||
|
||
**Live:** http://72.60.69.120:8765/index.html (port 8765, ufw allowed)
|
||
|
||
---
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
~/projects/valheim-map/
|
||
├── src/ # Source scripts
|
||
│ ├── valheim_db.py # Phase 1: Build POI SQLite database from locations.json
|
||
│ ├── decode_valheim_tiles.py # Phase 2: Decode .bin.gz tiles → PNG images
|
||
│ ├── build_viewer.py # Phase 2: Generate Leaflet.js web viewer
|
||
│ ├── parse_valheim_db.py # Phase 3: Attempted .db parser (WIP)
|
||
│ └── parse_valheim_zpackage.py # Phase 3: ZPackage format exploration (WIP)
|
||
├── data/ # Data files (small)
|
||
│ ├── valheim_poi.db # SQLite POI database (55 MB)
|
||
│ └── Nerdhalla.fwl # World metadata file (499 bytes)
|
||
├── output/ # Generated map assets
|
||
│ ├── index.html # Leaflet.js web viewer (5.4 MB)
|
||
│ ├── world_composite.png # Full world map, biome+height (3.7 MB)
|
||
│ ├── world_biome.png # Full world biome classification (374 KB)
|
||
│ ├── world_height.png # Full world heightmap (2.4 MB)
|
||
│ ├── XX-YY_*.png # Individual tile images (16 tiles × 3 layers)
|
||
│ └── explored.png # (future) Explored/unexplored overlay
|
||
├── notes/ # Reference notes
|
||
│ └── tile-format-readme.txt # Original valheim-map.world tile format spec
|
||
└── README.md # This file
|
||
```
|
||
|
||
**Large files not in repo** (stored in /tmp/):
|
||
- `/tmp/valheim_locations.json` — 52 MB raw location export
|
||
- `/tmp/Nerdhalla.db` — 85 MB world save from Nerdcade AMP container
|
||
- `/tmp/Nerdhalla.json` — 1.5 GB JSON conversion (valheim-save-tools)
|
||
- `/tmp/valheim_tiles/tiles/*.bin.gz` — 16 tile files, ~100 MB total
|
||
|
||
---
|
||
|
||
## Roadmap
|
||
|
||
### ✅ Phase 1 — POI Database (Complete)
|
||
|
||
**Script:** `src/valheim_db.py`
|
||
|
||
Parsed `locations.json` (52 MB) into a queryable SQLite database with spatial support.
|
||
|
||
**Tables:**
|
||
- `locations` — 11,309 entries (prefab name, category, coordinates, dungeon/loot flags)
|
||
- `important_contents` — 24,407 entries (veggisirs, surtling cores, treasure, spawners)
|
||
- `random_contents` — 457,617 entries (dungeon interior loot)
|
||
- `dungeon_components` — 49,338 entries (room layouts)
|
||
|
||
**Spatial queries work:**
|
||
- "Highest density of tar pits?" → Zone (1500, 3500) has 11 in 1000u radius
|
||
- "Most Surtling Cores?" → Crypt3 @ (2039, -1162) has 20 cores
|
||
- "Best base location for tar farming?" → (1210, -4159) — 5 tar pits in 200m, Plains biome
|
||
|
||
### ✅ Phase 2 — Web Map Viewer (Complete)
|
||
|
||
**Scripts:** `src/decode_valheim_tiles.py`, `src/build_viewer.py`
|
||
|
||
Decoded 16 tile files (4×4 grid, 1024×1024 samples each) into PNG images and built a Leaflet.js viewer.
|
||
|
||
**Features:**
|
||
- **3 map layers:** Composite (biome+height shading), Biome (9-color classification), Height (grayscale elevation)
|
||
- **11,309 POI markers** — color-coded by category, clickable with loot popups
|
||
- **Category legend** — toggle groups on/off (boss, dungeon, vegvisir, vendor, resource, etc.)
|
||
- **Search** — by prefab name or category
|
||
- **Coordinate display** — Valheim world coords in popups
|
||
|
||
**Layer toggle buttons** (bottom-left): Composite | Biome | Height
|
||
|
||
### ⏳ Phase 3 — Fog-of-War Overlay (Blocked)
|
||
|
||
**Goal:** Show explored vs unexplored areas on the map.
|
||
|
||
**Blocked by:** The explored map data is stored per-player in `.fch` character files on each player's local machine, NOT in the world `.db` file. The `.db` contains 1.8M ZDOs (world objects, terrain, structures) but no explored bitmap.
|
||
|
||
**What we have:**
|
||
- `Nerdhalla.db` (85 MB) — pulled from Nerdcade AMP container, parsed via valheim-save-tools
|
||
- `Nerdhalla.fwl` (499 bytes) — world settings (seed, presets, difficulty)
|
||
- ZPackage parser WIP in `src/parse_valheim_zpackage.py`
|
||
|
||
**What's needed:** Player `.fch` files from:
|
||
- Windows: `%USERPROFILE%\AppData\LocalLow\IronGate\Valheim\characters\`
|
||
- Linux: `~/.config/unity3d/IronGate/Valheim/characters/`
|
||
|
||
### 🔮 Phase 4 — Cartography Table Integration (Planned)
|
||
|
||
**Goal:** A shared map of truth for the server.
|
||
|
||
**Approach options (in order of preference):**
|
||
|
||
1. **ServerSideMap mod (BepInEx)** — Gold standard. Strips map data from client files, hosts on server in real-time. As you explore, fog clears for everyone. Pin sharing with dedup. Requires BepInEx on AMP Valheim instance + all players install the mod.
|
||
|
||
2. **Periodic merge script** — Collect `.fch` files from players, merge explored bitmaps (OR operation), deduplicate pins (same name + within 15m), write back clean files. Run on cron.
|
||
|
||
3. **Cartography table automation** — Lightweight BepInEx plugin that auto-triggers "Record Discoveries" near the table. Still manual but removes forgetfulness.
|
||
|
||
**Pin cleaning strategy:**
|
||
- Dedup by name + proximity (15m radius)
|
||
- "Last writer wins" for conflicting pins
|
||
- Ghost pin prevention: track pin origin, only remove if no active player has it
|
||
|
||
---
|
||
|
||
## Technical Details
|
||
|
||
### Tile Format
|
||
|
||
From the valheim-map.world export Readme:
|
||
|
||
```
|
||
struct MapSample {
|
||
uint16_t biome; // 2 bytes
|
||
float height; // 4 bytes
|
||
float forestFactor; // 4 bytes
|
||
} // 10 bytes per sample
|
||
```
|
||
|
||
- 1024 × 1024 samples per tile = 10,485,760 bytes uncompressed
|
||
- 4 × 4 tiles = 16 tiles, 24000 × 24000 Valheim units
|
||
- Gzip compressed individually
|
||
|
||
**Biome enum:** None=0, Meadows=1, Swamp=2, Mountain=4, BlackForest=8, Plains=16, AshLands=32, DeepNorth=64, Ocean=256, Mistlands=512
|
||
|
||
### Coordinate Mapping
|
||
|
||
```
|
||
Valheim coords: (-12000, -12000) to (+12000, +12000)
|
||
Image coords: (0, 0) to (4096, 4096)
|
||
|
||
pixel_x = (valheim_x + 12000) / 24000 * 4096
|
||
pixel_y = (valheim_z + 12000) / 24000 * 4096
|
||
```
|
||
|
||
### Zone System
|
||
|
||
From the [Valheim Wiki](https://valheim.fandom.com/wiki/Zones):
|
||
- Zones are 64m × 64m
|
||
- Generated: 9×9 around each player (288m radius)
|
||
- Loaded: 5×5 (160m radius)
|
||
- Active: 3×3 (96m radius)
|
||
- Current: the zone(s) containing the player
|
||
|
||
### Database Schema
|
||
|
||
```sql
|
||
-- Core locations table
|
||
CREATE TABLE locations (
|
||
id INTEGER PRIMARY KEY,
|
||
prefab_name TEXT NOT NULL,
|
||
category TEXT NOT NULL,
|
||
pos_x REAL, pos_y REAL, pos_z REAL,
|
||
has_dungeon INTEGER DEFAULT 0,
|
||
has_important INTEGER DEFAULT 0,
|
||
has_random INTEGER DEFAULT 0
|
||
);
|
||
|
||
-- Important contents (veggisirs, cores, treasure)
|
||
CREATE TABLE important_contents (
|
||
id INTEGER PRIMARY KEY,
|
||
location_id INTEGER NOT NULL,
|
||
friendly_name TEXT,
|
||
count INTEGER,
|
||
icon TEXT,
|
||
FOREIGN KEY (location_id) REFERENCES locations(id)
|
||
);
|
||
|
||
-- Random contents (dungeon interior loot)
|
||
CREATE TABLE random_contents (
|
||
id INTEGER PRIMARY KEY,
|
||
location_id INTEGER NOT NULL,
|
||
type TEXT, parent TEXT, name TEXT, interior INTEGER,
|
||
FOREIGN KEY (location_id) REFERENCES locations(id)
|
||
);
|
||
|
||
-- Dungeon components (room layouts)
|
||
CREATE TABLE dungeon_components (
|
||
id INTEGER PRIMARY KEY,
|
||
location_id INTEGER NOT NULL,
|
||
delta_x REAL, delta_y REAL, rotation_y REAL,
|
||
parent_type TEXT, parent_name TEXT,
|
||
FOREIGN KEY (location_id) REFERENCES locations(id)
|
||
);
|
||
```
|
||
|
||
### .db File Format (ZPackage)
|
||
|
||
The world `.db` file uses Unity's ZPackage binary serialization format:
|
||
|
||
```
|
||
int32: world_version
|
||
double: net_time
|
||
int64: my_id
|
||
uint32: next_uid
|
||
int32: num_zdos
|
||
ZDO[]: zone data objects (1,800,945 in Nerdhalla)
|
||
Zones: zone management data
|
||
RandomEvent: random event state
|
||
SHA512: 64-byte hash
|
||
```
|
||
|
||
Each ZDO contains: uid, prefab hash, owner, data type, revision, key-value pairs (floats, vectors, strings, byte arrays), position, rotation.
|
||
|
||
**Current world version:** 37 (valheim-save-tools max: 34 — parses with warning)
|
||
|
||
---
|
||
|
||
## Usage Examples
|
||
|
||
### Query the POI database
|
||
|
||
```bash
|
||
sqlite3 ~/projects/valheim-map/data/valheim_poi.db
|
||
|
||
# Find all tar pits
|
||
SELECT pos_x, pos_z FROM locations WHERE prefab_name LIKE '%TarPit%';
|
||
|
||
# Find boss altars
|
||
SELECT prefab_name, pos_x, pos_z FROM locations WHERE category='boss';
|
||
|
||
# Find dungeons with most surtling cores
|
||
SELECT l.prefab_name, l.pos_x, l.pos_z, SUM(ic.count) as total
|
||
FROM locations l JOIN important_contents ic ON l.id = ic.location_id
|
||
WHERE ic.friendly_name LIKE '%SurtlingCore%'
|
||
GROUP BY l.id ORDER BY total DESC LIMIT 10;
|
||
|
||
# Find vegvisirs
|
||
SELECT ic.friendly_name, l.pos_x, l.pos_z
|
||
FROM locations l JOIN important_contents ic ON l.id = ic.location_id
|
||
WHERE ic.friendly_name LIKE '%Vegvisir%';
|
||
```
|
||
|
||
### Rebuild from scratch
|
||
|
||
```bash
|
||
# 1. Build POI database
|
||
python3 src/valheim_db.py
|
||
|
||
# 2. Decode tiles to PNG
|
||
python3 src/decode_valheim_tiles.py
|
||
|
||
# 3. Build web viewer
|
||
python3 src/build_viewer.py
|
||
|
||
# 4. Serve
|
||
cd output && python3 -m http.server 8765
|
||
```
|
||
|
||
---
|
||
|
||
## Data Sources
|
||
|
||
| Source | File | Size | Origin |
|
||
|--------|------|------|--------|
|
||
| valheim-map.world export | `locations.json` | 52 MB | ROG (E:\Downloads\MapData_yzZ5fr2tGa\) |
|
||
| valheim-map.world export | `tiles/*.bin.gz` | ~100 MB | ROG |
|
||
| AMP Valheim container | `Nerdhalla.db` | 85 MB | Nerdcade (`docker cp AMP_Nerdhalla01:/AMP/Valheim/896660/Saves/worlds_local/`) |
|
||
| AMP Valheim container | `Nerdhalla.fwl` | 499 B | Nerdcade |
|
||
|
||
---
|
||
|
||
## Future Work
|
||
|
||
- [ ] **Fog-of-war overlay** — get `.fch` files from players, extract explored bitmap
|
||
- [ ] **ServerSideMap mod** — install BepInEx on AMP Valheim, deploy shared map
|
||
- [ ] **Pin cleaning/syncing** — dedup, merge, ghost pin prevention
|
||
- [ ] **Auto-update cron** — periodically pull `.db` from Nerdcade, regenerate map
|
||
- [ ] **Forgejo repo** — host this project in a self-hosted git instance
|
||
- [ ] **FoundryVTT export** — convert POI data for tabletop use
|
||
- [ ] **Mobile-friendly** — responsive layout for phone viewing
|