# Packaging & Shipping

Once your map is built, validated, and baked, it's time to publish it to the
world. Tactical Shooter uses the **PCK** (Godot Resource Pack) format — the map
is packaged as a `.pck` file, uploaded to a registry server, and clients
download it automatically when they join a server running that map.

## Pipeline Overview

```
┌──────────┐    ┌──────────────┐    ┌──────────────┐    ┌────────────┐
│ pack_map │ →  │ .pck + .json │ →  │ Map Registry │ →  │ Client     │
│ .gd      │    │ files        │ →  │ Server       │ →  │ Downloader │
└──────────┘    └──────────────┘    └──────────────┘    └────────────┘
   (editor          (artifacts)        (HTTP serving)     (autoload)
    tool)
```

## Step 1: Package the Map

The `pack_map.gd` script (at `scripts/map_packaging/pack_map.gd`) is a Godot
editor tool that exports a `.tscn` scene as a standalone `.pck` resource pack.

### Usage

From the **map template project** (not the main game project):

```bash
cd maps/my_first_map/

godot --headless --script /path/to/scripts/map_packaging/pack_map.gd \
  --map=res://de_my_map.tscn
```

> Replace `/path/to/` with the actual path to the tactical-shooter repository
> root, or copy `pack_map.gd` into your map project's `scripts/` directory.

### What It Does

1. Loads the specified `.tscn` scene
2. Recursively collects all dependencies (materials, textures, meshes, etc.)
3. Exports them to `user://packed_maps/de_my_map.pck`
4. Writes a metadata file `user://packed_maps/de_my_map.json`

### Output Files

```
user://packed_maps/
├── de_my_map.pck          # Binary resource pack (~1–10 MB for a typical map)
└── de_my_map.json         # JSON metadata (machine-readable)
```

**Metadata example** (`de_my_map.json`):
```json
{
    "map_name": "de_my_map",
    "source_scene": "res://de_my_map.tscn",
    "godot_version": {
        "major": 4,
        "minor": 2,
        "string": "4.2"
    },
    "packed_at": "2026-06-25T14:30:00"
}
```

### Requirements

- The map scene must use **local resources** only (relative `res://` paths)
- External dependencies (game shared assets like weapon models, player
  models) should stay in the base game — the `.pck` is **additive** content
- The `.pck` contains only the map's unique assets, not the entire game

### From the Editor

You can also run `pack_map.gd` from the Godot editor:

1. Open your map project in Godot
2. Open your map scene (`de_my_map.tscn`)
3. **Project → Tools → Pack Current Map** (if configured)
4. The script detects the active scene and exports it

## Step 2: Set Up the Map Registry Server

The registry server is a lightweight Python HTTP server
(`scripts/map_packaging/map_registry_server.py`) that:
- Serves `.pck` files for download
- Provides a JSON `/maps` endpoint listing available maps
- Auto-scans the maps directory every 5 seconds
- Computes and serves SHA-256 checksums for integrity verification

### Starting the Server

```bash
cd /path/to/tactical-shooter

# Default: port 8090, maps from ./packed_maps/
python3 scripts/map_packaging/map_registry_server.py

# Custom configuration
python3 scripts/map_packaging/map_registry_server.py \
  --port 8080 \
  --maps-dir /data/tactical-shooter-maps \
  --verbose
```

**Environment variables:**
| Variable | Overrides | Default |
|----------|-----------|---------|
| `MAP_REGISTRY_PORT` | `--port` / `-p` | 8090 |
| `MAP_REGISTRY_MAPS` | `--maps-dir` / `-d` | `./packed_maps/` |

### API Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `GET /` | — | Server info and endpoint documentation |
| `GET /maps` | — | JSON list of all available maps with metadata |
| `GET /maps/<name>.pck` | — | Download map `.pck` binary |
| `GET /maps/<name>.json` | — | Download per-map metadata |
| `OPTIONS /maps` | — | CORS preflight |

**Example responses:**

```
GET /maps
```
```json
{
    "maps": [
        {
            "name": "de_dust2",
            "size": 5432100,
            "version": 1,
            "description": "Classic competitive map",
            "checksum_sha256": "a1b2c3d4...",
            "packed_at": "2026-06-25T14:30:00"
        }
    ],
    "server_name": "Tactical Shooter Map Registry",
    "server_version": "1.0.0",
    "map_count": 1
}
```

```
GET /maps/de_dust2.pck
```
→ Returns the `.pck` binary file as an octet-stream download.

### Deployment Options

- **Local dev:** Run on the same machine as the game (default `127.0.0.1:8090`)
- **Server host:** Run on a public server alongside the master server
- **Docker:** Wrap in a Docker container for easy deployment
- **Systemd service:** Create a service file for production hosting

**Systemd service example:**
```ini
[Unit]
Description=Tactical Shooter Map Registry
After=network.target

[Service]
Type=simple
User=tactical
WorkingDirectory=/opt/tactical-shooter
ExecStart=/usr/bin/python3 scripts/map_packaging/map_registry_server.py \
  --port 8090 --maps-dir /data/tactical-maps
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

## Step 3: Upload Your Map

1. Copy the `.pck` and `.json` files to the registry server's maps directory:
   ```bash
   cp user://packed_maps/de_my_map.pck /data/tactical-maps/
   cp user://packed_maps/de_my_map.json /data/tactical-maps/
   ```
2. The server auto-detects new files within 5 seconds
3. Verify via `curl http://your-server:8090/maps`

## Step 4: Client Downloads

The client uses `MapDownloader` (autoload at `client/scripts/map_downloader.gd`)
to fetch and load maps at runtime.

### How It Works

1. Player joins a server that specifies a map name
2. The client checks its local cache (`user://maps/manifest.json`)
3. If the map is not cached, `MapDownloader.fetch_map_list()` polls
   the registry server to verify the map exists
4. `MapDownloader.download_map(name)` downloads the `.pck` to `user://maps/`
5. `ProjectSettings.load_resource_pack()` loads it into the game
6. The server changes to the new level

### Map Caching

```
user://maps/
├── de_dust2.pck              # Cached map binary
├── de_inferno.pck            # Another cached map
└── manifest.json             # Cache index
```

**Cache behaviour:**
- Maps are cached to disk after download (survives restarts)
- The manifest tracks version numbers for staleness checks
- `MapDownloader.remove_map(name)` clears a specific map
- `MapDownloader.clear_cache()` removes all cached maps

### Configuration

The `MapDownloader` autoload can be configured in-game:

```gdscript
# Set the registry server URL (override default http://127.0.0.1:8090)
MapDownloader.registry_url = "http://maps.myserver.com:8090"
```

The URL can also be set via the `MAP_REGISTRY_URL` environment variable,
which is read in `_ready()`.

### Signals

```gdscript
# Register for map lifecycle events
MapDownloader.map_list_loaded.connect(_on_map_list)
MapDownloader.map_download_progress.connect(_on_download_progress)
MapDownloader.map_download_complete.connect(_on_download_done)
MapDownloader.map_loaded.connect(_on_map_loaded)
```

## Full Pipeline Example

```bash
# 1. Package from the template project
cd maps/my_first_map
godot --headless --script ../../scripts/map_packaging/pack_map.gd \
  --map=res://de_my_map.tscn

# 2. Start registry server
cd ../..
python3 scripts/map_packaging/map_registry_server.py --port 8090 &

# 3. Deploy map
cp ~/.local/share/godot/app_userdata/MapTemplate/packed_maps/de_my_map.pck \
   scripts/map_packaging/packed_maps/
cp ~/.local/share/godot/app_userdata/MapTemplate/packed_maps/de_my_map.json \
   scripts/map_packaging/packed_maps/

# 4. Verify
curl -s http://localhost:8090/maps | python3 -m json.tool

# 5. In-game: connect to server and play the map
```

## Next Steps

- [FAQ & Troubleshooting](06-faq-and-troubleshooting.md) — common issues
