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.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user