Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
5.5 KiB
GDScript
140 lines
5.5 KiB
GDScript
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)
|