# 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