# 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
