Files
tactical-shooter/docs/mapmaking/04-validation.md
T
shawn 34507f9043 docs: community mapmaking documentation (7-file SDK guide)
- 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
2026-07-01 18:47:06 -04:00

7.6 KiB
Raw Permalink Blame History

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

# 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

#!/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_<name>.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("<name>", instance, scene_path) call in validate_map.gd:_ready()

Next Steps