- 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
7.6 KiB
Map Validation
The validator catches structural problems, performance budget violations, and lighting issues before your map ships. Two validation modes are available:
- Editor validation — runs automatically when you open a map scene
- 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:
- Open your map's
.tscnfile in the Godot editor - Look at the Output panel (bottom dock)
- 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
# 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 ares://-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
#!/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
Node3Dtype WorldEnvironmentnode present and named correctlyLightmapGInode 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
MeshInstance3Dnodes ≤ 50,000 - Per-mesh triangle breakdown printed (helps identify heavy assets)
- Warns on individual meshes > 5,000 triangles
- Warns if no
MeshInstance3Dnodes 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, andShaderMaterialtexture 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_distancecoverage - 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:
- Create
client/tools/validate_map/validate_<name>.gdextendingRefCounted - Implement
func validate(scene_root: Node, scene_path: String) -> Dictionary - Return
{"pass": bool, "errors": [String], "warnings": [String]} - Add a
_run_module("<name>", instance, scene_path)call invalidate_map.gd:_ready()
Next Steps
- Packaging & Shipping — ship your validated map