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
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user