Files
mtgodot-poc/project/main_character_sync_test.gd
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

117 lines
3.8 KiB
GDScript

extends SceneTree
const Scene = preload("res://game_scene.gd")
const View = preload("res://ui/player_view.gd")
class Client extends Node:
var main := 0
var rows := {}
func get_main_vid() -> int: return main
func get_entity(vid: int) -> Dictionary: return rows.get(vid, {})
class StubView extends Node3D:
var anim: Node
var succeeds := true
var during_build := Callable()
func build(_assets: String, _race: int, _pump: Callable) -> bool:
if during_build.is_valid(): during_build.call()
return succeeds
func set_audio(_node: Node) -> void: pass
class Harness extends Scene:
var builds := 0
var succeeds := true
var during_build := Callable()
func _create_main_view() -> Node3D:
builds += 1
var view := StubView.new()
view.succeeds = succeeds
view.during_build = during_build
return view
class Controller extends Node:
signal anim_state(state: String)
var player: Node3D
class Camera extends Camera3D:
var target: Node3D
var failures := 0
func check(value: bool, label: String) -> void:
if not value:
failures += 1
printerr("FAIL: " + label)
func _initialize() -> void:
call_deferred("run")
func run() -> void:
var client := Client.new()
var scene := Harness.new()
root.add_child(client)
root.add_child(scene)
scene.set_process(false)
scene.client = client
scene._assets = "synthetic-assets"
scene.player = Node3D.new()
scene.add_child(scene.player)
scene.pc = Controller.new()
scene.add_child(scene.pc)
scene.cam = Camera.new()
scene.add_child(scene.cam)
scene._sync_main_character()
check(scene.builds == 0, "setup incomplete does not build")
scene._main_sync_ready = true
client.main = 10
scene._sync_main_character()
check(scene.builds == 0, "late entity is awaited")
client.rows[10] = {"pos": Vector3(20, 0, -30)}
scene._sync_main_character()
check(scene.builds == 0, "missing race does not build")
check(scene.player.position == Vector3(20, 0, 30), "late row places placeholder")
client.rows[10].race = 0
scene.during_build = func(): scene._on_main_set(10)
scene._sync_main_character()
check(scene.builds == 1 and scene._model_built, "reentrant network event creates one view")
check(scene.cam.target == scene.player and scene.pc.player == scene.player, "replacement updates consumers")
scene.player.position = Vector3(25, 0, 35)
for i in 5:
scene._sync_main_character()
scene._on_main_set(10)
check(scene.builds == 1, "duplicate main events are idempotent")
check(scene.player.position == Vector3(25, 0, 35), "polling does not undo movement")
client.main = 20
client.rows[20] = {"race": 1, "pos": Vector3(40, 0, -50)}
scene.during_build = Callable()
scene.succeeds = false
scene._sync_main_character()
check(scene.builds == 2 and not scene._model_built, "failed replacement is not marked ready")
for i in 5: scene._sync_main_character()
check(scene.builds == 2, "failure does not retry every frame")
scene._main_retry_at = 0
scene.succeeds = true
scene._sync_main_character()
check(scene.builds == 3 and scene._model_built, "transient failure recovers")
check(scene.pc.anim_state.get_connections().size() == 1, "replacement does not duplicate animation subscriptions")
var previous = scene.player
client.main = 30
client.rows[30] = {"race": 0, "pos": Vector3.ZERO}
scene.during_build = func(): client.main = 40
scene._sync_main_character()
check(scene.player == previous, "obsolete build is discarded")
client.rows[40] = {"race": 0, "pos": Vector3.ZERO}
scene.during_build = Callable()
scene.succeeds = false
for i in 5:
scene._main_retry_at = 0
scene._sync_main_character()
check(scene._main_attempts == 3, "permanent failure has bounded attempts")
var view := View.new()
check(not view.build("missing-assets", 8), "unsupported race rejected")
view.free()
scene.queue_free()
client.queue_free()
await process_frame
print("main_character_sync_test: failures=", failures)
quit(1 if failures else 0)