@tool extends Node # Map Validator — Command-line entry point. # # Usage: # godot --path client --script tools/validate_map.gd -- res://maps/mymap.tscn # # The scene path must come after `--` so Godot passes it as a user argument. # # Exit codes: # 0 — All checks passed # 1 — Warnings only (light suggestions, no blocked items) # 2 — Errors found (must-fix items) # # Runs four validation modules in order: # 1. Scene structure — node hierarchy, required groups, LightmapGI, WorldEnvironment # 2. Polygon count — total mesh triangles ≤ 50K # 3. Texture size — all material textures ≤ 1024×1024 # 4. Light count — dynamic (non-baked) lights ≤ 4, lightmap bake status # # Each module returns a dictionary {pass: bool, errors: [str], warnings: [str]}. const VALIDATOR_DIR := "res://tools/validate_map/" enum ExitCode { PASS = 0, WARNINGS = 1, ERRORS = 2, } func _ready() -> void: var args := OS.get_cmdline_user_args() if args.is_empty(): _print_usage() get_tree().quit(ExitCode.ERRORS) return var scene_path := args[0] # Validate path prefix if not scene_path.begins_with("res://"): push_error("Scene path must be a Godot resource path (res://) — got: ", scene_path) get_tree().quit(ExitCode.ERRORS) return # Load the scene var scene := ResourceLoader.load(scene_path, "PackedScene", ResourceLoader.CACHE_MODE_IGNORE) if scene == null: push_error("Failed to load scene: ", scene_path) get_tree().quit(ExitCode.ERRORS) return print("═══════════════════════════════════════════") print(" MAP VALIDATOR") print(" Scene: ", scene_path) print("═══════════════════════════════════════════") print("") # Instantiate the scene (in @tool mode this runs in editor) var instance_data: Array = _try_instantiate(scene, scene_path) var instance: Node = instance_data[0] as Node var instantiate_error: String = instance_data[1] as String if instance == null: push_error(instantiate_error) get_tree().quit(ExitCode.ERRORS) return add_child(instance) # Run all validators var results: Array[Dictionary] = [] results.append(_run_module("validate_scene", instance, scene_path)) results.append(_run_module("validate_polycount", instance, scene_path)) results.append(_run_module("validate_textures", instance, scene_path)) results.append(_run_module("validate_lights", instance, scene_path)) # Summary print("") print("═══════════════════════════════════════════") print(" SUMMARY") print("═══════════════════════════════════════════") var total_errors := 0 var total_warnings := 0 for result in results: var label: String = "PASS" if result["pass"] else result["status"] print(" [", label, "] ", result["name"]) total_errors += result["errors"].size() total_warnings += result["warnings"].size() print("") print(" Errors: ", total_errors) print(" Warnings: ", total_warnings) if total_errors > 0: print(" Result: FAIL") print("") get_tree().quit(ExitCode.ERRORS) elif total_warnings > 0: print(" Result: PASS (with warnings)") print("") get_tree().quit(ExitCode.WARNINGS) else: print(" Result: PASS") print("") get_tree().quit(ExitCode.PASS) # Attempts to instantiate a PackedScene, catching errors gracefully. func _try_instantiate(scene: PackedScene, path: String) -> Array: var err_test := scene.can_instantiate() if not err_test: return [null, "Scene cannot be instantiated (may depend on other resources): " + path] var inst := scene.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE) if inst == null: return [null, "Scene instantiate returned null: " + path] return [inst, ""] # Loads a validator script from VALIDATOR_DIR and runs its checks. func _run_module(name: String, scene_root: Node, scene_path: String) -> Dictionary: var script_path := VALIDATOR_DIR.path_join(name + ".gd") var script := ResourceLoader.load(script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE) if script == null: push_error("Failed to load validator module: ", script_path) return { "name": name, "pass": false, "status": "ERROR", "errors": ["Validator script not found: " + script_path], "warnings": [], } # Instantiate the validator module directly (scripts extend RefCounted) if not script.has_method("validate"): push_error("Validator module missing validate() method: ", script_path) return { "name": name, "pass": false, "status": "ERROR", "errors": ["Missing validate() method in " + script_path], "warnings": [], } print("── [", name, "] ────────────────────────────") var validator = script.new() var result: Dictionary = validator.validate(scene_root, scene_path) # Print inline results for w in result.get("warnings", []): print(" ⚠ ", w) for e in result.get("errors", []): print(" ✖ ", e) var ok: bool = result.get("pass", false) if ok: print(" ✓ pass") else: print(" ✖ FAIL") print("") return result func _print_usage() -> void: print("Map Validator — usage:") print("") print(" godot --path --script tools/validate_map.gd -- ") print("") print("Examples:") print(" godot --path client --script tools/validate_map.gd -- res://maps/de_dust2.tscn") print(" godot --path client --script tools/validate_map.gd -- res://maps/my_map.tscn") print("") print("Exit codes:") print(" 0 — All checks passed") print(" 1 — Warnings (non-blocking suggestions)") print(" 2 — Errors (must-fix items)") print("")