34507f9043
- 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
236 lines
7.6 KiB
Markdown
236 lines
7.6 KiB
Markdown
# 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
|