From 34507f9043537cdc455b6428afc86a5613471a0d Mon Sep 17 00:00:00 2001 From: Shawn Date: Wed, 1 Jul 2026 18:46:10 -0400 Subject: [PATCH] docs: community mapmaking documentation (7-file SDK guide) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/mapmaking/00-index.md — SDK overview with workflow diagram - docs/mapmaking/01-getting-started.md — template setup & first map - docs/mapmaking/02-building-geometry.md — CSG guide & prefab reference - docs/mapmaking/03-lighting-and-env.md — LightmapGI baking guide - docs/mapmaking/04-validation.md — validator CLI & CI/CD usage - docs/mapmaking/05-packaging-and-shipping.md — .pck pipeline - docs/mapmaking/06-faq-and-troubleshooting.md — 30+ common issues - README.md — add mapmaking SDK link to features list --- README.md | 3 +- docs/mapmaking/00-index.md | 104 ++++++++ docs/mapmaking/01-getting-started.md | 135 ++++++++++ docs/mapmaking/02-building-geometry.md | 245 +++++++++++++++++ docs/mapmaking/03-lighting-and-env.md | 159 +++++++++++ docs/mapmaking/04-validation.md | 235 +++++++++++++++++ docs/mapmaking/05-packaging-and-shipping.md | 264 +++++++++++++++++++ docs/mapmaking/06-faq-and-troubleshooting.md | 238 +++++++++++++++++ 8 files changed, 1382 insertions(+), 1 deletion(-) create mode 100644 docs/mapmaking/00-index.md create mode 100644 docs/mapmaking/01-getting-started.md create mode 100644 docs/mapmaking/02-building-geometry.md create mode 100644 docs/mapmaking/03-lighting-and-env.md create mode 100644 docs/mapmaking/04-validation.md create mode 100644 docs/mapmaking/05-packaging-and-shipping.md create mode 100644 docs/mapmaking/06-faq-and-troubleshooting.md diff --git a/README.md b/README.md index c8d5dc1..364a9ad 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ A competitive, round-based tactical FPS built in Godot 4. - 128-tick authoritative dedicated servers - GDExtension (C++) simulation core - Community-hostable, self-hosted server browser -- Map-making SDK for community maps +- [Map-making SDK](docs/mapmaking/00-index.md) for community maps See [PLANS/PROJECT_PLAN.md](PLANS/PROJECT_PLAN.md) for the full build plan. +See [Mapmaking SDK docs](docs/mapmaking/00-index.md) to build and ship custom maps. diff --git a/docs/mapmaking/00-index.md b/docs/mapmaking/00-index.md new file mode 100644 index 0000000..ff391c5 --- /dev/null +++ b/docs/mapmaking/00-index.md @@ -0,0 +1,104 @@ +# Tactical Shooter — Mapmaking SDK + +Everything you need to build, validate, package, and ship competitive maps for +Tactical Shooter. This SDK is designed for the community — no C++, no GDextension +tinkering required. You only need Godot 4 (any 4.x) and a text editor. + +## Quick Start + +```bash +# Copy the template project +cp -r client/map_template/ maps/my_first_map/ + +# Rename the demo scene +mv maps/my_first_map/template_map.tscn maps/my_first_map/de_my_map.tscn + +# Open in Godot +godot maps/my_first_map/project.godot +``` + +Then read the [Getting Started guide](01-getting-started.md). + +## What You Can Build + +- **Competitive defuse maps** — A-site, B-site, CT/T spawns, buy zones +- **Custom game modes** — The template is gameplay-agnostic; your map logic + is in Godot groups, not hardcoded +- **Community servers** — Ship maps as `.pck` files via the registry server + +## SDK Components + +| Component | Location | Purpose | +|-----------|----------|---------| +| [Template project](01-getting-started.md) | `client/map_template/` | Standalone Godot project with CSG prefabs and demo scene | +| [CSG Prefabs](02-building-geometry.md) | `client/map_template/assets/prefabs/` | 6 drop-in node types (spawns, sites, zones) | +| [Editor Validator](04-validation.md) | `client/map_template/template_map.gd` | Auto-runs when scene opened in editor | +| [Headless Validator](04-validation.md) | `client/tools/validate_map.gd` | 4-module CLI validator for CI/CD | +| [Packaging](05-packaging-and-shipping.md) | `scripts/map_packaging/pack_map.gd` | Exports `.tscn` → `.pck` addon pack | +| [Registry Server](05-packaging-and-shipping.md) | `scripts/map_packaging/map_registry_server.py` | Serves `.pck` files over HTTP | +| [Client Downloader](05-packaging-and-shipping.md) | `client/scripts/map_downloader.gd` | Autoload that downloads + loads `.pck` at runtime | + +## Map Authoring Workflow + +``` +┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ 1. Build │ ──→ │ 2. Validate │ ──→ │ 3. Package │ +│ - CSG geometry │ │ - Editor check │ │ - pack_map.gd │ +│ - Prefab nodes │ │ - CLI validator │ │ - → .pck file │ +│ - Lighting │ │ - Fix warnings │ │ │ +└──────────────────┘ └──────────────────┘ └──────────────────┘ + │ + ▼ +┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ +│ 6. Load & Play │ ←── │ 5. Download │ ←── │ 4. Upload │ +│ - Auto-download │ │ - Registry HTTP │ │ - map_registry │ +│ - changelevel │ │ - Cache to disk │ │ - .pck + .json │ +└──────────────────┘ └──────────────────┘ └──────────────────┘ +``` + +## Performance Targets + +Maps built with the SDK must meet these budgets: + +| Metric | Target | Enforced by | +|------------------|-------------|--------------------------------| +| Triangle count | ≤ 50,000 | `validate_polycount.gd` | +| Texture size | ≤ 1024×1024 | `validate_textures.gd` | +| Dynamic lights | ≤ 4 | `validate_lights.gd` | +| LightmapGI | ✓ Baked | `validate_lights.gd` | +| ReflectionProbe | ≥ 1 | `validate_lights.gd` (warning) | +| FPS target | 60+ | Project rendering settings | + +## File Structure Reference + +``` +tactical-shooter/ +├── client/ +│ ├── project.godot # Main game project +│ ├── map_template/ # ← YOU ARE HERE (template project) +│ │ ├── project.godot # Standalone Godot project +│ │ ├── template_map.tscn # Demo scene (rename me) +│ │ ├── template_map.gd # Editor validation script +│ │ └── assets/prefabs/ # CSG prefab nodes +│ ├── tools/ +│ │ ├── validate_map.gd # CLI validator entry point +│ │ └── validate_map/ # 4 validator modules +│ │ ├── validate_scene.gd +│ │ ├── validate_polycount.gd +│ │ ├── validate_textures.gd +│ │ └── validate_lights.gd +│ └── scripts/ +│ └── map_downloader.gd # Client autoload for .pck loading +├── scripts/ +│ └── map_packaging/ +│ ├── pack_map.gd # Godot editor → .pck exporter +│ └── map_registry_server.py # Python HTTP registry server +└── docs/mapmaking/ + ├── 00-index.md # This document + ├── 01-getting-started.md # Template project setup + ├── 02-building-geometry.md # CSG & prefab guide + ├── 03-lighting-and-env.md # Lighting & baking + ├── 04-validation.md # Validator usage + ├── 05-packaging-and-shipping.md# Publishing pipeline + └── 06-faq-and-troubleshooting.md +``` diff --git a/docs/mapmaking/01-getting-started.md b/docs/mapmaking/01-getting-started.md new file mode 100644 index 0000000..f74aaef --- /dev/null +++ b/docs/mapmaking/01-getting-started.md @@ -0,0 +1,135 @@ +# Getting Started — Your First Map + +This guide walks you through setting up the map template project and creating +a basic playable map. + +## Prerequisites + +- Godot 4.0 or later (any 4.x stable) +- The Tactical Shooter repository cloned +- Familiarity with Godot's editor (scene tree, inspector, 3D viewport) + +## 1. Copy the Template + +The template project lives at `client/map_template/`. Copy it somewhere outside +the main project (or keep it adjacent — it's a standalone Godot project): + +```bash +# From the tactical-shooter repository root +cp -r client/map_template/ maps/my_first_map/ +cd maps/my_first_map/ +mv template_map.tscn de_dust2.tscn +``` + +## 2. Open in Godot + +Launch Godot and click **Import** (not Open). Navigate to your copied directory +and select `project.godot`. The map template project will open. + +> **Why "Import"?** Because template is a standalone project with its own +> `project.godot`. You're importing a new project into the Godot project list, +> not opening a scene within an existing project. + +## 3. Explore the Demo Scene + +Open `de_dust2.tscn` (the renamed `template_map.tscn`). You'll see: + +- **Floor**: A green CSGBox3D pad (16×20 units) +- **Walls**: Orange CSGBox3D walls forming a rough box +- **Spawn pads**: Green (CT) and red (T) Marker3D spawn points +- **Bomb sites**: Semi-transparent orange floor panels +- **Buy zones**: Semi-transparent yellow trigger areas +- **Lighting**: DirectionalLight3D (sun), OmniLight3D fill, LightmapGI +- **Environment**: WorldEnvironment with sky + +The scene also contains a `template_map.gd` editor tool script. When you open +this scene, the **Output** panel (bottom dock) shows auto-validation results +for the map structure. + +## 4. Build Your First Room + +1. **Select the floor** — Click `Floor` in the scene tree +2. **Scale it** — With the scale tool (R), drag the red/green/blue handles + to resize the floor to roughly 12×16 units +3. **Add walls** — Select `Walls` → right-click → **Instantiate Child Scene** → + select `assets/prefabs/map_bounds.tscn`. Size them to enclose the floor +4. **Add a ceiling** — Duplicate (Ctrl+D) the floor, move it up 4 units, + set **Operation** to Union (0) so it acts as a solid ceiling + +> **Tip:** Use **View → Perspective** and rotate around to check your geometry +> from all angles. Walls should overlap slightly to prevent light leaks. + +## 5. Place Spawn Points + +Spawn points tell the game where players appear at round start. + +**CT Spawns:** +- Drag `assets/prefabs/ct_spawn.tscn` from the FileSystem dock into the scene +- Position it on the floor at one end of the map +- The green pad's +Z arrow shows the direction players face on spawn +- Duplicate for 5 players (competitive standard) + +**T Spawns:** +- Same process with `assets/prefabs/t_spawn.tscn` (red pads) +- Place at the opposite end of the map + +## 6. Define Bomb Sites + +A competitive defuse map needs two bomb sites, A and B. + +1. Drag `assets/prefabs/bomb_site.tscn` into the scene +2. Rename it to `BombsiteA` +3. Resize the CollisionShape3D to cover the intended site area +4. In the **Node** dock → **Groups** tab, ensure it has: + - `bomb_site` (required — game logic finds sites by this group) + - `bombsite_a` (informational — helps identify which site) +5. Repeat for BombsiteB with group `bombsite_b` + +## 7. Add Buy Zones + +Buy zones mark areas where players can purchase equipment. + +1. Drag `assets/prefabs/buy_zone.tscn` into the scene +2. Place one at the CT spawn area (group `buy_zone` stays) +3. Resize the yellow CollisionShape3D to cover the spawn room +4. Duplicate for T spawn — add subgroup `ct_buy` or `t_buy` on each + +## 8. Check the Editor Validator + +Open the **Output** (bottom dock). The `template_map.gd` script runs +automatically. You should see something like: + +``` +=== Map Template: Validate Scene === + + CT Spawn points (5 found) + + T Spawn points (5 found) + + Buy zones (2 found) + + Bomb sites (2 found) + + Cubemap capture origins (1 found) + + Map boundary walls (8 found) + + CSG Floor geometry (1 nodes) + + CSG Wall geometry (4 walls) + + LightmapGI configured ✓ + + LightmapGI: NOT YET BAKED + + ReflectionProbe present + + WorldEnvironment present + + DirectionalLight3D (sun) present +``` + +The "NOT YET BAKED" warning is expected — you'll bake lighting after geometry +is finalised (see [Lighting guide](03-lighting-and-env.md)). + +## 9. Save and Package + +When your map is ready for testing: + +1. Save the scene (`Ctrl+S`) +2. Read the [Validation guide](04-validation.md) for headless CI checks +3. Read the [Packaging guide](05-packaging-and-shipping.md) to ship it + +## Next Steps + +- [Building Geometry with CSG](02-building-geometry.md) — detailed prefab reference +- [Lighting & Baking](03-lighting-and-env.md) — how to light your map properly +- [Validator Reference](04-validation.md) — CLI validator exit codes and CI +- [Packaging & Shipping](05-packaging-and-shipping.md) — publishing as .pck diff --git a/docs/mapmaking/02-building-geometry.md b/docs/mapmaking/02-building-geometry.md new file mode 100644 index 0000000..7ed321a --- /dev/null +++ b/docs/mapmaking/02-building-geometry.md @@ -0,0 +1,245 @@ +# Building Geometry with CSG + +Tactical Shooter maps are built with Godot's **CSG** (Constructive Solid +Geometry) system — the same tool you'd use in a 3D modeller, but inside the +Godot editor. CSG nodes let you cut, join, and extrude geometry in real-time +with instant visual feedback. + +## CSG Basics + +### CSG Node Types + +| Node | Use | Shape | +|------|-----|-------| +| `CSGBox3D` | Walls, floors, ceilings, pillars | Rectangular prism | +| `CSGSphere3D` | Domes, curved edges | Sphere | +| `CSGCylinder3D` | Columns, pipes | Cylinder | +| `CSGCombiner3D` | Group container, non-collidable parent | — | +| `CSGPolygon3D` | Extruded 2D path | Custom shape | + +### CSG Operations + +Each CSG node has an **Operation** property that determines how it combines +with sibling CSG nodes in the same parent: + +| Value | Operation | Effect | +|-------|-----------|--------| +| 0 | **Union** | Adds to existing geometry (default) | +| 1 | **Subtraction** | Cuts a hole through parent | +| 2 | **Intersection** | Keeps only overlapping volume | + +**Example — a window cutout:** + +``` +Wall (CSGBox3D, Union) +└── WindowCutout (CSGBox3D, Subtraction) +``` + +This subtracts the window shape from the wall. Move/resize the subtraction +child to reposition the window. + +### Key Properties + +- **`use_collision`** (bool) — Must be `true` on all floor/wall/ceiling CSG + nodes so players and bullets collide with them +- **`use_shadows`** (bool) — Enable shadow-casting on geometry +- **`material`** — Assign placeholder materials during construction; final + materials come from the main game project library +- **`snap_mode`** — Controls vertex snapping (useful for aligning geometry) + +## Building a Room + +A basic room needs 3 CSG layers: + +``` +Room (CSGCombiner3D) +├── Floor (CSGBox3D, Union, use_collision=true) +├── Walls (CSGCombiner3D) +│ ├── Wall_North (CSGBox3D, Union) +│ ├── Wall_South (CSGBox3D, Union) +│ ├── Wall_East (CSGBox3D, Union) +│ └── Wall_West (CSGBox3D, Union) +└── Ceiling (CSGBox3D, Union, use_collision=true) +``` + +### CSG Building Tips + +1. **Overlap slightly** — Walls should extend past the floor edge by ~0.1 units + to prevent light leaks (thin gaps let light bleed through) +2. **Keep CSG simple** — Use as few CSG nodes as possible. A wall panel is one + CSGBox3D, not 4 extruded edges +3. **Convert to Mesh when done** — Once geometry is final, select all CSG + nodes → right-click → **Convert CSG to Mesh**. This bakes them into static + MeshInstance3D nodes that perform much better +4. **Avoid curved CSG for floor/walls** — CSGSphere3D and CSGCylinder3D are + expensive. Use them sparingly + +## Prefab Reference + +The template provides 6 drag-and-drop prefab nodes. Each is an `assets/prefabs/*.tscn` +file that you instantiate into your scene. + +### ct_spawn.tscn + +| Property | Value | +|----------|-------| +| Type | `Marker3D` (green pad + arrow) | +| Group | `ct_spawn` | +| Purpose | CT team spawn position | +| Quantity | 5 (one per player) | + +The +Z arrow (blue axis) shows the direction players face when they spawn. +Position the pad at floor level — make it flush with the floor surface. + +**Customisation:** Duplicate instances rather than editing the prefab. Adjust +only position and rotation. The green pad is cosmetic. + +### t_spawn.tscn + +| Property | Value | +|----------|-------| +| Type | `Marker3D` (red pad + arrow) | +| Group | `t_spawn` | +| Purpose | Terrorist team spawn position | +| Quantity | 5 | + +Same usage as `ct_spawn` but red. Place at the opposite end of the map. + +### buy_zone.tscn + +| Property | Value | +|----------|-------| +| Type | `Area3D` with `CollisionShape3D` | +| Group | `buy_zone` | +| Purpose | Purchase-eligible zone | +| Quantity | 1–2 | + +The game monitors `body_entered` / `body_exited` on these areas to show/hide +the buy menu. Resize the child CollisionShape3D to match the room shape. + +**Additional groups (optional):** +- `ct_buy` — Only CT team can buy in this zone +- `t_buy` — Only T team can buy in this zone + +If omitted, any player inside a `buy_zone` can buy (suitable for shared mid areas). + +### bomb_site.tscn + +| Property | Value | +|----------|-------| +| Type | `Area3D` with `CollisionShape3D` | +| Group | `bomb_site` | +| Purpose | Bomb plant zone | +| Quantity | 2 (A and B) | + +Rename instances to `BombsiteA` and `BombsiteB`. Resize the CollisionShape3D +to cover the plantable area. Visual: semi-transparent orange floor panel. + +**Additional groups (strongly recommended):** +- `bombsite_a` — Identifies this as site A +- `bombsite_b` — Identifies this as site B + +### cubemap_origin.tscn + +| Property | Value | +|----------|-------| +| Type | `Node3D` (blue sphere marker) | +| Group | `cubemap_origin` | +| Purpose | Marks ReflectionProbe capture position | +| Quantity | 1 | + +Place at eye height ≈ 1.6 units above the floor in the most visually prominent +area (usually mid-map or the central choke point). The ReflectionProbe is a +separate node that should be placed at this marker's position after geometry +is finalised. + +### map_bounds.tscn + +| Property | Value | +|----------|-------| +| Type | `Area3D` with `CollisionShape3D` | +| Group | `map_bounds` | +| Purpose | Out-of-bounds kill wall | +| Quantity | 4–8 (enclose the perimeter) | + +**Usage:** +- Place one instance per perimeter face (north, south, east, west) +- Resize each CollisionShape3D to form a closed box around the playable area +- Make the floor-bound wall tall enough that players can't jump over it +- The game detects `body_exited` from these areas and teleports/kills + out-of-bounds players + +> **Performance tip:** `map_bounds` nodes should be as thin as practical — +> these are just detection triggers, not visual geometry. A thickness of 0.5 +> units is sufficient. + +## Advanced Geometry Techniques + +### Corridors and Hallways + +Build a corridor as a CSGCombiner3D with union floor + walls + ceiling and +subtraction nodes for doors/windows: + +``` +Corridor (CSGCombiner3D) +├── Floor (CSGBox3D, Union) +├── LeftWall (CSGBox3D, Union) +│ └── Door1 (CSGBox3D, Subtraction) +├── RightWall (CSGBox3D, Union) +│ └── Window1 (CSGBox3D, Subtraction) +└── Ceiling (CSGBox3D, Union) +``` + +### Ramps and Stairs + +Use multiple CSGBox3D nodes at increasing heights. Stack them like steps and +wrap in a CSGCombiner3D for a clean hierarchy: + +``` +Stairs (CSGCombiner3D) +├── Step1 (CSGBox3D, Union, pos=(0, 0, 0)) +├── Step2 (CSGBox3D, Union, pos=(0, 0.5, 1)) +├── Step3 (CSGBox3D, Union, pos=(0, 1.0, 2)) +└── Step4 (CSGBox3D, Union, pos=(0, 1.5, 3)) +``` + +For ramps, rotate a CSGBox3D or use CSGPolygon3D with an extruded triangle shape. + +### Map Scale Reference + +| Element | Size (units) | Notes | +|---------|-------------|-------| +| Player height | 1.8 | Eye level ≈ 1.6 | +| Player width | 0.5 | Collision capsule radius | +| Doorway | 1.5 × 2.5 | Wide enough for 2 players | +| Corridor | 3–4 wide | Comfortable movement | +| Bombsite | 8×8 minimum | Playable site area | +| Spawn room | 6×8 | 5 players need space | +| Mid area | 6–8 wide | Main chokepoint | + +## From CSG to MeshInstance3D (Baking) + +When your geometry is **final** (no more changes expected): + +1. Select all CSG nodes in the scene tree +2. Right-click → **Convert CSG to Mesh** +3. This replaces CSG nodes with static `MeshInstance3D` nodes +4. The mesh instances are much more performant + +**Don't convert until you're done** — CSG is editable, MeshInstance3D is not. +Keep working in CSG mode; convert only for final optimisation before packaging. + +## Performance Budget + +See the [Validator guide](04-validation.md) for detailed numbers, but the key +geometry rules are: + +- **≤ 50,000 triangles** total across all meshes +- **≤ 5,000 triangles** per individual mesh (recommended; enforced as warning) +- Use CSG for blockout only — convert to mesh before shipping +- Avoid CSGSphere3D in visible areas; use hand-modelled meshes + +## Next Steps + +- [Lighting & Environment Setup](03-lighting-and-env.md) — light your map +- [Map Validation](04-validation.md) — run the validator on your scene diff --git a/docs/mapmaking/03-lighting-and-env.md b/docs/mapmaking/03-lighting-and-env.md new file mode 100644 index 0000000..5d4434a --- /dev/null +++ b/docs/mapmaking/03-lighting-and-env.md @@ -0,0 +1,159 @@ +# Lighting & Environment Setup + +Proper lighting is the difference between a map that looks professional and one +that looks like a grey box. Tactical Shooter uses Godot's **LightmapGI** system +for baked global illumination: light bounces are pre-calculated and stored in +lightmaps, giving you realistic lighting at zero runtime cost. + +## Lighting Architecture + +``` +DirectionalLight3D (sun) — main directional light, shadows + └─ OmniLight3D (fill) — ambient fill lights, no shadow + └─ LightmapGI — bakes all static lighting into textures + └─ baked lightmaps (zero runtime cost) +``` + +## 1. WorldEnvironment + +Every map needs a `WorldEnvironment` node. This controls: + +- **Sky** — Background appearance (skybox or solid colour) +- **Ambient light** — Base light level for unlit surfaces +- **Tonemapping** — Colour grading and exposure + +**Setup:** +1. The template already includes a `WorldEnvironment` node +2. If creating from scratch, add one via the scene tree → + button → `WorldEnvironment` +3. In the Inspector, assign an `Environment` resource (New Environment) +4. Configure: + - **Background:** Sky (for outdoor) or Clear Color (for underground maps) + - **Ambient Light:** Sky (mimics sky colour) or Color (custom ambient) + - **Tonemap:** Reinhard or Filmic (prevents blown-out highlights) + +> The validator warns if WorldEnvironment is missing — add it early. + +## 2. DirectionalLight3D (Sun) + +The main sun light. One per map. + +**Recommended settings:** +| Property | Value | Reason | +|----------|-------|--------| +| `light_bake_mode` | Static (2) | Bakes into lightmap | +| `shadow_enabled` | true | Required for competitive visibility | +| `shadow_bias` | 0.1 | Prevents shadow acne on CSG | +| `angular_distance` | 0.01 | Sharp sunlight (competitive visual clarity) | +| `range` | 50–100 | Large enough to cover the map | + +**Positioning:** +- Rotate the sun to create interesting shadows through windows/doorways +- Avoid straight-down lighting (makes everything look flat) +- A 45° angle works well for most maps + +## 3. Fill Lights (OmniLight3D) + +Fill lights illuminate areas the sun doesn't reach (interiors, tunnels). + +**Rules:** +- **Maximum 4 dynamic (non-baked) lights** — enforced by the validator +- Fill lights should use `light_bake_mode = Static (2)` so they bake into + the lightmap and don't count toward the dynamic budget +- Use low intensity (0.3–0.5) for ambient fill + +**Performance tip:** Every omni/spot light with `shadow_enabled = true` is +expensive. The validator warns on dynamic lights with shadows. Baked lights +with shadows disabled are free — use them. + +## 4. LightmapGI — Baked Global Illumination + +LightmapGI is the backbone of Tactical Shooter's lighting. It pre-computes +light bouncing and stores the result as textures (lightmaps). + +### Configuration + +| Property | Recommended | Notes | +|----------|-------------|-------| +| `quality` | 2 (High) | Higher = better GI, longer bake | +| `bounces` | 3 | Minimum 2 for realistic indirect lighting | +| `texel_scale` | 1.0–2.0 | Lower = higher res, longer bake | +| `max_texture_size` | 1024 | Controls lightmap atlas size | +| `use_denoiser` | true | Removes noise from baked result | +| `environment_mode` | 0 (Baked Environment) | Uses WorldEnvironment for sky lighting | + +### Baking + +To bake: select the `LightmapGI` node → click **Bake Lightmap** in the top +toolbar (or `Ctrl+Shift+C`). + +**Baking checklist:** +- [ ] All geometry is finalised (moving geometry after baking invalidates it) +- [ ] All lights have correct bake mode (Static for baked, Dynamic for real-time) +- [ ] CSG has `use_collision = true` (non-collidable CSG may not bake properly) +- [ ] WorldEnvironment is configured with a sky or ambient colour +- [ ] Save the scene before baking + +**Baking time:** Varies by map complexity. A small map (10×14 units) bakes in +30–60 seconds. A full competitive map (20×20) may take 5–10 minutes. + +### After Baking + +- The LightmapGI node's `light_data` property will show a `LightmapGIData` resource +- The validator checks for `light_data != null` to confirm the map is baked +- Baked lightmaps are stored in the `.tscn` file as embedded resources +- You can rebake at any time — it overwrites the previous lightmaps + +**Warning:** Opening an unbaked map on a server will default to unlit rendering. +Always bake before packaging. + +## 5. ReflectionProbe + +ReflectionProbes add specular reflections to shiny surfaces (metal, glass, water). + +- Place one `ReflectionProbe` near the `cubemap_origin` marker +- Set `max_distance` to cover the visible area (10–30 units) +- Enable `box_projection` for interior spaces (corrects reflection distortion) +- Set `intensity` to 0.5–0.8 (realistic reflection strength) + +**Tip:** You need reflection probes even if your map has no mirrors — they make +metal and plastic materials look correct. + +## 6. Lighting Performance Budget + +| Metric | Limit | Enforced | +|--------|-------|----------| +| Dynamic (non-baked) lights | ≤ 4 | Validator (error) | +| Total lights (baked + dynamic) | Unlimited | — | +| LightmapGI bounces | ≥ 2 | Validator (warning) | +| ReflectionProbes | ≥ 1 | Validator (warning) | +| Lightmap max texture | ≤ 2048 | Project settings | + +If you exceed the dynamic light budget, set more lights to +`light_bake_mode = Static (2)` — baked lights cost nothing at runtime. + +## 7. Quick Start Checklist + +- [ ] WorldEnvironment with Environment resource assigned +- [ ] DirectionalLight3D (sun) with shadows +- [ ] 1–3 fill OmniLight3D with Static bake mode +- [ ] LightmapGI with quality=2, bounces=3, texel_scale=1.0 +- [ ] ReflectionProbe at cubemap origin +- [ ] All geometry CSG has `use_collision = true` +- [ ] Save scene → Bake LightmapGI (Ctrl+Shift+C) +- [ ] Verify `light_data` is populated (not null) + +## Troubleshooting + +| Problem | Likely Cause | Fix | +|---------|-------------|-----| +| Light leaks at corners | CSG gap < 0.01 units | Overlap CSG edges by 0.1 | +| Baked lighting too dark | bounces too low | Set bounces ≥ 2 | +| Lightmap seams visible | texel_scale too high | Lower texel_scale to 1.0 | +| Shadow acne (striped shadows) | shadow_bias too low | Increase shadow_bias to 0.1 | +| ReflectionProbe visible as sphere | probe_mode wrong | Set to Box or disable visibility | +| Bake fails with error | Scene not saved | Save `.tscn` before baking | + +## Next Steps + +- [Map Validation](04-validation.md) — run all checks on your scene +- [Packaging & Shipping](05-packaging-and-shipping.md) — publish your map diff --git a/docs/mapmaking/04-validation.md b/docs/mapmaking/04-validation.md new file mode 100644 index 0000000..1906937 --- /dev/null +++ b/docs/mapmaking/04-validation.md @@ -0,0 +1,235 @@ +# Map Validation + +The validator catches structural problems, performance budget violations, and +lighting issues before your map ships. Two validation modes are available: + +1. **Editor validation** — runs automatically when you open a map scene +2. **Headless CLI validation** — runs from the terminal, suitable for CI/CD + +## Editor Validation (template_map.gd) + +Every map created from the template includes `template_map.gd`, a `@tool` +script that runs automatically when the scene opens in the Godot editor. + +**How to use it:** +1. Open your map's `.tscn` file in the Godot editor +2. Look at the **Output** panel (bottom dock) +3. The script prints a validation report like: + ``` + === Map Template: Validate Scene === + + CT Spawn points (5 found) + + T Spawn points (5 found) + + Buy zones (2 found) + + Bomb sites (2 found) + + Cubemap capture origins (1 found) + + Map boundary walls (8 found) + + LightmapGI configured ✓ + + LightmapGI: NOT YET BAKED + + DirectionalLight3D (sun) present + ``` + +**Checks performed:** +- Required gameplay groups exist (`ct_spawn`, `t_spawn`, `buy_zone`, etc.) +- CSG floor and wall geometry present +- LightmapGI configured and baked +- ReflectionProbe and WorldEnvironment present +- DirectionalLight3D (sun) present +- Playable area extents estimated + +## Headless CLI Validator (validate_map.gd) + +The headless validator is a 4-module CLI tool at `client/tools/validate_map.gd`. +Use it for automated checking before packaging or in CI/CD pipelines. + +### Usage + +```bash +# From the tactical-shooter repository root +godot --path client --script tools/validate_map.gd -- res://maps/mymap.tscn +``` + +> The `--` separates Godot engine arguments from user arguments. +> The scene path must be a `res://`-prefixed Godot resource path. + +### Exit Codes + +| Code | Meaning | Description | +|------|---------|-------------| +| 0 | **PASS** | All checks passed, no warnings | +| 1 | **WARNINGS** | Passed with non-blocking suggestions | +| 2 | **ERRORS** | Failed — must-fix items present | + +### CI/CD Integration + +```bash +#!/bin/bash +# Gate map merges on validation + +godot --path client --script tools/validate_map.gd -- res://maps/mymap.tscn +exit_code=$? + +if [ $exit_code -eq 2 ]; then + echo "❌ Map validation FAILED — fix errors before submitting" + exit 1 +elif [ $exit_code -eq 1 ]; then + echo "⚠️ Map passed with warnings — review suggestions" +fi + +echo "✅ Map validated successfully" +exit 0 +``` + +### Example Output + +``` +═══════════════════════════════════════════ + MAP VALIDATOR + Scene: res://maps/de_dust2.tscn +═══════════════════════════════════════════ + +── [validate_scene] ──────────────────────────── + ✓ Root node: World (Node3D) + ✓ WorldEnvironment (WorldEnvironment) + ✓ LightmapGI (LightmapGI) + ✓ Group "ct_spawn" → CT_SpawnPos (5 node(s)) + ✓ Group "t_spawn" → T_SpawnPos (5 node(s)) + ✓ Group "buy_zone" → BuyZone_A (2 node(s)) + ✓ Group "bomb_site" → BombsiteA (2 node(s)) + ✓ Group "cubemap_origin" → CubemapOrigin (1 node(s)) + ✓ pass + +── [validate_polycount] ──────────────────────── + Meshes scanned: 24 + Total triangles: 12,842 + Budget: 50,000 + ✓ Within budget (25.7% of 50000) + Top meshes by triangle count: + 4200 Building_01 (MeshInstance3D) + 2100 Wall_Detail (MeshInstance3D) + 1042 Floor_Tiles (MeshInstance3D) + ✓ pass + +── [validate_textures] ───────────────────────── + Textures inspected: 18 + ✓ All textures within 1024×1024 limit + ✓ pass + +── [validate_lights] ─────────────────────────── + Light breakdown: + DirectionalLight3D: 1 + OmniLight3D: 2 + SpotLight3D: 0 + Dynamic (non-baked): 1 + Baked: 2 + ✓ Dynamic light count: 1 (limit: 4) + ✓ LightmapGI baked (light_data present) + Quality: 2 (0=Low, 1=Med, 2=High, 3=Ultra) + Bounces: 3 + Texel Scale: 1.0 + Max Texture Size: 1024 + ✓ WorldEnvironment configured + ReflectionProbes: 1 + ✓ pass + +═══════════════════════════════════════════ + SUMMARY +═══════════════════════════════════════════ + [PASS] validate_scene + [PASS] validate_polycount + [PASS] validate_textures + [PASS] validate_lights + + Errors: 0 + Warnings: 0 + Result: PASS +``` + +## Validator Modules + +### 1. Scene Structure (`validate_scene.gd`) + +**What it checks:** +- Scene root is a `Node3D` type +- `WorldEnvironment` node present and named correctly +- `LightmapGI` node present and named correctly +- All 5 required groups assigned (`ct_spawn`, `t_spawn`, `buy_zone`, `bomb_site`, `cubemap_origin`) +- Node names follow PascalCase convention (no spaces) +- No duplicate required groups on different nodes + +**Typical errors:** +``` +✖ Missing required group: "ct_spawn" +✖ Missing WorldEnvironment — map needs a WorldEnvironment node +✖ Node name contains spaces: "My Room Section" +``` + +### 2. Polygon Count (`validate_polycount.gd`) + +**What it checks:** +- Total triangle count across all `MeshInstance3D` nodes ≤ **50,000** +- Per-mesh triangle breakdown printed (helps identify heavy assets) +- Warns on individual meshes > **5,000** triangles +- Warns if no `MeshInstance3D` nodes found at all + +**Typical errors:** +``` +✖ Map exceeds 50,000 triangle budget: 68,420 total (36.8% over budget) +``` + +### 3. Texture Sizes (`validate_textures.gd`) + +**What it checks:** +- All material textures ≤ **1024×1024** +- Checks `StandardMaterial3D`, `ORMMaterial3D`, and `ShaderMaterial` texture params +- Warns on textures > **512×512** (suggested for secondary surfaces) +- LightmapGI textures checked separately (baked textures can be larger) + +**Typical errors:** +``` +✖ Texture exceeds 1024×1024: floor_tiles.png (albedo) — 2048× +``` + +### 4. Lights & Lightmap (`validate_lights.gd`) + +**What it checks:** +- Dynamic (non-baked) OmniLight3D + SpotLight3D ≤ **4** +- DirectionalLight3D counts toward dynamic budget if not baked +- LightmapGI present and baked (`light_data != null`) +- Lightmap quality, bounces, texel scale, max texture size +- WorldEnvironment assigned with Environment resource +- ReflectionProbe presence and `max_distance` coverage +- Warns on dynamic lights with shadow enabled (performance cost) + +**Typical errors:** +``` +✖ Too many dynamic lights: 6 (max 4) +✖ No LightmapGI node found +✖ No WorldEnvironment node found +``` + +## Passing the Validator + +The goal is exit code 0: **PASS**, no errors, no warnings. + +| Code | Action | +|------|--------| +| 0 (PASS) | Your map is ready to package | +| 1 (WARNINGS) | Fix suggestions before shipping (non-blocking) | +| 2 (ERRORS) | Fix all errors — map must pass before packaging | + +> **CI gate rule:** Treat exit code 2 as a hard block. Exit code 1 is advisory +> but should be reviewed. Only exit code 0 means "ship it." + +## Adding a Custom Validator Module + +Advanced users can extend the validator: + +1. Create `client/tools/validate_map/validate_.gd` extending `RefCounted` +2. Implement `func validate(scene_root: Node, scene_path: String) -> Dictionary` +3. Return `{"pass": bool, "errors": [String], "warnings": [String]}` +4. Add a `_run_module("", instance, scene_path)` call in + `validate_map.gd:_ready()` + +## Next Steps + +- [Packaging & Shipping](05-packaging-and-shipping.md) — ship your validated map diff --git a/docs/mapmaking/05-packaging-and-shipping.md b/docs/mapmaking/05-packaging-and-shipping.md new file mode 100644 index 0000000..9644343 --- /dev/null +++ b/docs/mapmaking/05-packaging-and-shipping.md @@ -0,0 +1,264 @@ +# 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/.pck` | — | Download map `.pck` binary | +| `GET /maps/.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 diff --git a/docs/mapmaking/06-faq-and-troubleshooting.md b/docs/mapmaking/06-faq-and-troubleshooting.md new file mode 100644 index 0000000..6f3cbe8 --- /dev/null +++ b/docs/mapmaking/06-faq-and-troubleshooting.md @@ -0,0 +1,238 @@ +# FAQ & Troubleshooting + +Common questions, issues, and solutions encountered while building maps. + +## Template Project + +### Q: Can I use the template project without the main game repo? + +Yes. The template project at `client/map_template/` is a **standalone Godot +project**. Copy it anywhere, open it in Godot, and start building. You only +need the main repo for packaging (`pack_map.gd`) and the registry server +(`map_registry_server.py`). + +### Q: Why does Godot say "Import" instead of "Open"? + +The template has its own `project.godot`, making it a separate project. Use +**Import** to add it to your Godot project list. You're not opening a scene +within an existing project. + +### Q: How do I add the main game's assets to my map? + +The template uses placeholder materials. For final maps: + +1. Copy your map's `.tscn` into the main project under `client/maps//` +2. Open it from the main project's scenes tab +3. The main game's modular kit and PBR materials will be available +4. Assign materials to your CSG/MeshInstance3D nodes from the main project's + asset library + +### Q: Can I use external models from Blender/Maya? + +Yes. Export as `.glb` or `.obj` and import into Godot. Keep triangle counts +within budget (≤ 50K total, ≤ 5K per mesh recommended). Place imported meshes +as children of the map scene root. + +## Geometry & CSG + +### Q: My map has light leaks at the corners + +Thin gaps between CSG blocks let light bleed through. Fix by overlapping CSG +edges by **0.1 units** instead of butting them flush. + +``` +Bad: Wall ends at x=10.0, floor ends at x=10.0 → gap +Good: Wall extends to x=10.1, floor ends at x=10.0 → overlap +``` + +### Q: Players can walk through walls + +Make sure `use_collision = true` is set on all CSG geometry nodes. CSG nodes +don't have collision by default — you must enable it. + +### Q: My map renders as a grey box / no materials + +Placeholder materials are normal during construction. For the final look, you +need to: +1. Copy the map to the main project (see above) +2. Assign materials from the game's asset library +3. Re-bake LightmapGI after materials are assigned + +### Q: CSG performance is bad in the editor + +This is normal. CSG is re-calculated every time you move a node. For testing, +convert stable geometry to MeshInstance3D: +- Right-click CSG parent → **Convert CSG to Mesh** +- Keep a backup copy of the CSG hierarchy in another scene + +### Q: How do I make a skybox? + +Add a `WorldEnvironment` node and assign an Environment resource: +1. Set **Background** mode to **Sky** +2. Create a new **Sky** resource +3. Assign a **PanoramaSky** or **ProceduralSky** material +4. The sky appears behind your geometry + +### Q: My map has no collision after CSG → Mesh conversion + +When you convert CSG to MeshInstance3D, collision is not preserved. Add a +`CollisionShape3D` as a child of each MeshInstance3D, or use Godot's +**Mesh → Create Trimesh Collision** (right-click the MeshInstance3D). + +## Lighting + +### Q: My lightmap bake takes forever + +Baking time depends on: +- **texel_scale** — lower = higher resolution = longer bake (try 2.0 for testing) +- **bounces** — fewer bounces = faster bake (use 1 for quick tests, 3 for final) +- **Map size** — larger maps bake longer +- **Light count** — more lights = more calculations + +For quick iteration, use Low quality (0) during development and High quality (2) +for the final bake. + +### Q: Baked lighting is too dark + +- Increase `bounces` to 3 (more light bounce = brighter interiors) +- Add a low-intensity ambient fill (OmniLight3D with Static bake mode) +- Lower the `texel_scale` for more lightmap resolution +- Check that the DirectionalLight3D intensity is sufficient + +### Q: Lightmap has visible seams + +Seams appear where lightmap texels don't align. Fixes: +- Lower `texel_scale` to 1.0 (higher resolution lightmaps) +- Ensure CSG edges overlap (no gaps) +- Convert CSG to MeshInstance3D before final bake + +### Q: Shadows look blocky or jagged + +This is Godot's shadow mapping at default resolution. For competitive clarity: +- Keep `shadow_bias` at 0.1 (higher = softer shadows, fewer artefacts) +- Directional shadow `max_distance` at 20–30 (shadows beyond this use lower + resolution) + +### Q: I see a sphere artifact on my ReflectionProbe + +The ReflectionProbe has a `probe_mode` property — if set to **Reflection Probe** +(sphere mode), it renders as a visible sphere in reflections. Change to **Box +Probe** for interior spaces, or disable reflection visibility via the `visible` +property on the probe's MeshInstance3D child (Godot 4.x). + +### Q: Can I have a day/night cycle? + +No. LightmapGI bakes lighting statically. Dynamic lighting is limited to +≤ 4 real-time lights. Day/night cycles are outside the current scope. + +## Validation + +### Q: The validator says "No MeshInstance3D nodes found" + +Your map is using CSG nodes that haven't been converted to meshes. The +validator counts mesh triangles — CSG nodes are scanned as MeshInstance3D +after conversion. Either: +1. Convert CSG → Mesh for the area you want validated (right-click → Convert CSG to Mesh) +2. This warning is expected during early construction stages + +### Q: The validator fails with "Scene cannot be instantiated" + +The scene depends on resources that don't exist in the template project. This +usually happens when the map references game assets from the main project. +Either: +1. Run the validator from the **main game project** (`--path client` with the + main `client/project.godot`), or +2. Strip external dependencies — the map should be self-contained + +### Q: The validator reports texture errors but my textures are 1024×1024 + +Check for textures with different aspect ratios. A 2048×512 texture fails +because the larger dimension (2048) exceeds 1024. Resize or tile. + +### Q: Can I skip certain validator checks? + +Not directly. Run the validator, fix errors, then fix warnings. If a check +produces a false positive, the `@tool` scripts are in +`client/tools/validate_map/` — you can edit the constants (e.g. +`MAX_TOTAL_TRIANGLES`) for custom pipelines. + +## Packaging + +### Q: The .pck file is very large + +The `.pck` includes all dependencies of the scene. Large files mean: +- High-resolution textures (use ≤ 1024×1024 per the budget) +- High-poly meshes (stay under 50K triangles) +- Embedded audio or other large assets + +Run the validator first to check budgets. + +### Q: I get "Failed to load pack" in-game + +Possible causes: +- `.pck` file is corrupted (check SHA-256 checksum) +- The map scene or a dependency path changed between packaging and loading +- The `.pck` was built with a different Godot version than the game +- The resource path in the `.pck` conflicts with a path in the base game + +**Solution:** Re-package the map from the same Godot version the game uses. +Verify with `curl` that the download matches the server's checksum. + +### Q: Can I update a map without changing the client version? + +Yes. The `.pck` is additive content. Update the `.pck` on the registry server +and increment the `version` field in the `.json` metadata. The client will +re-download when it notices the version mismatch. + +### Q: Can I host the registry server on a different port? + +Yes. Use `--port ` or the `MAP_REGISTRY_PORT` environment variable. +The client's `MapDownloader` has a `registry_url` property you can set. + +### Q: Can I load more than one .pck at a time? + +Yes. `ProjectSettings.load_resource_pack()` is additive. Multiple `.pck` files +can be loaded simultaneously — each adds its resources to the global namespace. + +### Q: The registry server returns 404 for my map + +- Check that the `.pck` file is in the maps directory (default `./packed_maps/`) +- Check the file extension is `.pck` (lowercase) +- The server auto-scans every 5 seconds — wait or send SIGHUP to force a scan +- Verify by listing: `curl http://localhost:8090/maps` + +## General + +### Q: Can I make a hostage rescue map? + +The SDK is gameplay-agnostic. The groups `ct_spawn`, `t_spawn`, `bomb_site`, +and `buy_zone` are the default set. You can add your own groups (e.g., +`hostage`, `hostage_zone`) — the game logic discovers them at runtime. +Check the game's plugin API documentation for custom group support. + +### Q: Can I use this SDK for a different game? + +The SDK is designed for Tactical Shooter's Godot project structure. The +packaging and validation scripts are reusable for any Godot map-authoring +workflow, but the group conventions and gameplay nodes are game-specific. + +### Q: The editor validator doesn't run + +The `template_map.gd` script uses `@tool` and checks `Engine.is_editor_hint()`. +Make sure: +1. The script is attached to the root node of the scene +2. You're editing the scene in the Godot editor (not running the game) +3. The Output panel is visible (Window → Output) + +### Q: How do I report a bug in the SDK? + +Open an issue on the project's Gitea repository. Include: +- SDK version (commit hash) +- Godot version +- Steps to reproduce +- Log output (editor or CLI validator) + +### Q: Can I contribute validator modules? + +Yes. See [Validation — Adding a Custom Validator Module](04-validation.md#adding-a-custom-validator-module). +Submit a pull request with your module and updated documentation.