extends SceneTree ## Read-only verification after Godot has imported the Phase 3 assets. ## Run: godot --headless --path . --script res://tools/verify_phase3_imports.gd const MANIFEST_PATH: String = "res://assets/phase3_manifest.json" const EXPECTED_ASSET_COUNT: int = 66 const ALPHA_TOLERANCE: float = 0.02 var _errors: Array[String] = [] var _checked: int = 0 var _loaded: int = 0 func _initialize() -> void: call_deferred("_run") func _run() -> void: if not FileAccess.file_exists(MANIFEST_PATH): _fail("manifest", "File is missing: " + MANIFEST_PATH) _finish() return var file: FileAccess = FileAccess.open(MANIFEST_PATH, FileAccess.READ) if file == null: _fail("manifest", "Cannot open file: %s" % FileAccess.get_open_error()) _finish() return var parser: JSON = JSON.new() var parse_error: Error = parser.parse(file.get_as_text()) file.close() if parse_error != OK: _fail("manifest", "JSON line %d: %s" % [parser.get_error_line(), parser.get_error_message()]) _finish() return var parsed: Variant = parser.data if not parsed is Dictionary: _fail("manifest", "Root must be an object.") _finish() return var manifest: Dictionary = parsed if manifest.get("phase") != 3: _fail("manifest", "Expected phase 3.") var entries_value: Variant = manifest.get("assets") if not entries_value is Array: _fail("manifest", "assets must be an array.") _finish() return var entries: Array = entries_value if entries.size() != EXPECTED_ASSET_COUNT: _fail("manifest", "Expected %d assets, found %d." % [EXPECTED_ASSET_COUNT, entries.size()]) var seen_ids: Dictionary = {} for index: int in range(entries.size()): _checked += 1 var entry_value: Variant = entries[index] if not entry_value is Dictionary: _fail("entry_%d" % index, "Asset entry must be an object.") continue var entry: Dictionary = entry_value var asset_id: String = str(entry.get("id", "")) if asset_id.is_empty(): _fail("entry_%d" % index, "Asset ID is missing.") continue if seen_ids.has(asset_id): _fail(asset_id, "Duplicate asset ID.") seen_ids[asset_id] = true _verify_asset(asset_id, entry) _finish() func _verify_asset(asset_id: String, entry: Dictionary) -> void: for required_key: String in ["kind", "resource_path", "width", "height", "import_size_limit", "requires_alpha"]: if not entry.has(required_key): _fail(asset_id, "Missing manifest field: " + required_key) return var resource_path: String = str(entry["resource_path"]) var native_size: Vector2i = Vector2i(int(entry["width"]), int(entry["height"])) var size_limit: int = int(entry["import_size_limit"]) if native_size.x <= 0 or native_size.y <= 0 or size_limit < 0: _fail(asset_id, "Invalid native size or import limit.") return if not resource_path.begins_with("res://") or not ResourceLoader.exists(resource_path): _fail(asset_id, "Resource is missing: " + resource_path) return var texture: Texture2D = ResourceLoader.load(resource_path) as Texture2D if texture == null: _fail(asset_id, "Resource did not load as Texture2D: " + resource_path) return _loaded += 1 var expected_size: Vector2i = _expected_size(native_size, size_limit) var texture_size: Vector2i = Vector2i(texture.get_width(), texture.get_height()) if texture_size != expected_size: _fail(asset_id, "Texture size %s; expected %s (native %s, limit %d)." % [texture_size, expected_size, native_size, size_limit]) var texture_image: Image = texture.get_image() if texture_image == null or texture_image.is_empty(): _fail(asset_id, "Texture2D.get_image() returned no pixel data.") return if texture_image.get_size() != expected_size: _fail(asset_id, "Image size %s; expected %s." % [texture_image.get_size(), expected_size]) if not bool(entry["requires_alpha"]): return if texture_image.is_compressed(): # Decompress the returned in-memory Image only; never save or reimport it. var decompress_error: Error = texture_image.decompress() if decompress_error != OK: _fail(asset_id, "Cannot inspect compressed image alpha: %s" % decompress_error) return if texture_image.detect_alpha() == Image.ALPHA_NONE: _fail(asset_id, "Image has no effective alpha transparency.") var corner_alpha: float = texture_image.get_pixel(0, 0).a if corner_alpha > ALPHA_TOLERANCE: _fail(asset_id, "Corner alpha %.4f; expected at most %.4f." % [corner_alpha, ALPHA_TOLERANCE]) if str(entry["kind"]) == "ui_frame": var center_x: int = floori(float(texture_image.get_width()) * 0.5) var center_y: int = floori(float(texture_image.get_height()) * 0.5) var center_alpha: float = texture_image.get_pixel(center_x, center_y).a if center_alpha > ALPHA_TOLERANCE: _fail(asset_id, "Panel center alpha %.4f; expected at most %.4f." % [center_alpha, ALPHA_TOLERANCE]) func _expected_size(native_size: Vector2i, size_limit: int) -> Vector2i: if size_limit == 0: return native_size var longest_side: int = maxi(native_size.x, native_size.y) var scale_factor: float = minf(1.0, float(size_limit) / float(longest_side)) return Vector2i( maxi(1, roundi(float(native_size.x) * scale_factor)), maxi(1, roundi(float(native_size.y) * scale_factor)) ) func _fail(asset_id: String, message: String) -> void: _errors.append("[%s] %s" % [asset_id, message]) func _finish() -> void: for message: String in _errors: printerr("FAIL: " + message) var result_label: String = "PASS" if _errors.is_empty() else "FAIL" print("%s: Phase 3 imports: %d entries checked, %d/%d textures loaded, %d errors." % [result_label, _checked, _loaded, EXPECTED_ASSET_COUNT, _errors.size()]) quit(0 if _errors.is_empty() else 1)