Files
mtgodot-poc/project/gamescene_visual_test.gd
T
shenandshen c3a6fb973e fix(rendering): checkpoint main-character recovery and visual regression work
Handle late main-character data, guarded model construction, bounded retries and map correction. Validate player model sources and add lifecycle and real-asset visual regressions.

Include pending material and character-selection changes, updated A1 screenshot, and the detailed rendering repair plan. Hair occlusion, full UI parity and final macOS package acceptance remain unfinished.
2026-09-08 08:52:30 +08:00

73 lines
3.5 KiB
GDScript

# Real GameScene/assets, delayed authoritative row, no network or account writes.
extends SceneTree
const GameScene = preload("res://game_scene.gd")
const Fixtures = preload("res://gamescene_test.gd")
var failed := false
func _initialize() -> void:
call_deferred("run")
func run() -> void:
root.size = Vector2i(1280, 720)
var output := OS.get_environment("MT_RENDER_OUTPUT")
if output.is_empty():
output = ProjectSettings.globalize_path("res://../build/rendering/gamescene-%d" % Time.get_unix_time_from_system())
DirAccess.make_dir_recursive_absolute(output)
var fc := Fixtures.FakeClient.new()
root.add_child(fc)
var scene := GameScene.new()
root.add_child(scene)
await scene.setup(fc, AssetRoot.path(), "OutdoorA1/metin2_map_a1")
# Choose C1 to exercise late map correction too (setup initially had no VID).
var use_c1 := "--c1" in OS.get_cmdline_user_args()
var server := Vector2(959109, 269267) if use_c1 else Vector2(454600, 934000)
fc.main = 1000
fc.ents[1000] = {"vid": 1000, "name": "RenderTest", "race": 0,
"pos": Vector3(server.x * 0.01, 0, -server.y * 0.01),
"pos_cm": server, "is_main": true, "angle_deg": 0.0,
"func": 0, "moving": false, "hp": 100, "max_hp": 100, "dead": false}
for i in 30: await process_frame
var expected := MapCoord.to_world(fc.ents[1000].pos)
expected.y = scene.world.sample_height(expected.x, expected.z)
failed = not scene._model_built or scene.player.position.distance_to(expected) > 0.02
if use_c1: failed = failed or scene.map_path != "OutdoorC1/metin2_map_c1"
var surfaces := 0
for mi in scene.player.find_children("*", "MeshInstance3D", true, false):
if mi.mesh: surfaces += mi.mesh.get_surface_count()
failed = failed or surfaces == 0 or not scene._map_loaded()
if scene._model_built and scene.player.anim:
scene.player.anim.set_time(0.5)
scene.player.anim.set_process(false)
scene.cam.snap_to_target()
if DisplayServer.get_name() != "headless":
await RenderingServer.frame_post_draw
failed = failed or root.get_texture().get_image().save_png(output.path_join("game.png")) != OK
var report := {"passed": not failed, "map": scene.map_path, "server_cm": str(server),
"world_position": str(scene.player.position), "expected": str(expected),
"surfaces": surfaces, "map_report": scene.world.get_load_report(),
"engine": Engine.get_version_info(), "viewport": str(root.size),
"camera_transform": str(scene.cam.global_transform), "fov": scene.cam.fov,
"assets": AssetRoot.path(), "headless": DisplayServer.get_name() == "headless",
"scope": "real GameScene, delayed main row, no main signal; no server/40250 parity"}
var hashes := {}
for path in ["res://game_scene.gd", "res://ui/player_view.gd", "res://net/mapcoord.gd",
"res://gamescene_visual_test.gd", "res://bin/libmtgodot.macos.template_debug.dylib"]:
hashes[path] = FileAccess.get_sha256(path)
if scene._model_built:
for property in ["gr2_path", "hair_gr2", "hair_skin"]:
var path := String(scene.player.model.get(property))
if not path.is_empty(): hashes[path] = FileAccess.get_sha256(path)
var motion := String(scene.player.anim.get("anim_path")) if scene.player.anim else ""
if not motion.is_empty(): hashes[motion] = FileAccess.get_sha256(motion)
report["sha256"] = hashes
report["animation_sample_seconds"] = 0.5
var file := FileAccess.open(output.path_join("report.json"), FileAccess.WRITE)
file.store_string(JSON.stringify(report, "\t"))
file.close()
print("GAMESCENE_VISUAL: ", JSON.stringify(report))
scene.queue_free()
fc.queue_free()
await process_frame
quit(1 if failed else 0)