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.
117 lines
3.8 KiB
GDScript
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)
|