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

236 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_<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
- [Packaging & Shipping](05-packaging-and-shipping.md) — ship your validated map