extends SceneTree ## Does character selection work as part of every loadout? ## ## The character picker used to be a separate global screen. It now belongs to ## each loadout, so this checks the full-screen editor, saved character fields, ## selected-item summary, and animated preview together. ## ## godot --headless --path . -s res://debug/character_picker_check.gd var _fails: int = 0 func _init() -> void: root.call_deferred("add_child", Node.new()) # let autoloads finish _ready await process_frame await process_frame var menu = root.get_node_or_null("PauseMenu") _check(menu != null, "PauseMenu autoload exists") if menu == null: _done() return menu.visible = true menu._show_loadouts() await process_frame _check(menu.loadout_editor.visible, "Loadout editor shows") _check(not menu.main_vbox.visible, "Main pause list hides behind it") _check(menu.loadout_editor.anchor_right == 1.0 and menu.loadout_editor.anchor_bottom == 1.0, "Loadout editor is anchored full-screen") _check(menu.loadout_list_vbox.get_child_count() == 5, "All five loadouts are shown") _check(menu.skin_opt != null and menu.skin_opt.item_count > 0, "Character selector is inside the loadout editor") var skin_mgr = root.get_node("SkinManager") var loadout_mgr = root.get_node("LoadoutManager") for loadout_index in loadout_mgr.loadouts.size(): var loadout: Dictionary = loadout_mgr.loadouts[loadout_index] _check(loadout.has("skin"), "'%s' stores its own character" % loadout.get("name", "Loadout")) menu._edit_loadout(loadout_index) _check(menu._selected_metadata(menu.skin_opt) == str(loadout.get("skin", "default")), "'%s' opens with its saved character selected" % loadout.get("name", "Loadout")) _check(menu.loadout_summary.text.contains(menu._weapon_name(str(loadout.get("primary_1", "none")))), "'%s' summary displays its selected items" % loadout.get("name", "Loadout")) var count: int = menu.skin_opt.item_count _check(count > 0, "Roster is not empty (%d entries)" % count) var seen_glb := 0 for i in count: var id: String = menu.skin_opt.get_item_metadata(i) menu.skin_opt.select(i) menu._on_skin_option_selected(i) await process_frame await process_frame _check(menu.character_desc.text != "", "'%s' has a description line" % id) _check(menu.loadout_summary.text.contains(skin_mgr.get_skin(id).skin_name), "'%s' appears in the selected-item summary" % id) var skin = skin_mgr.get_skin(id) var expects_model: bool = skin.model_path != "" \ and ResourceLoader.exists(skin.model_path) if not expects_model: # A colour-tint skin has no GLB. The preview must be EMPTY, not the # previously selected character left standing there. _check(menu._preview_model == null, "'%s' is a colour skin and clears the preview" % id) continue seen_glb += 1 var model = menu._preview_model _check(model != null, "'%s' builds a preview model" % id) if model == null: continue _check(model.loaded, "'%s' preview finished loading" % id) _check(model.skeleton != null, "'%s' preview has a skeleton" % id) _check(model.animation_player != null, "'%s' preview has animations" % id) _check(model.surface_table() != null and not model.surface_table().is_empty(), "'%s' preview knows its surface classes" % id) var body: Array = model.surfaces_of(SkinSurfaces.BODY) _check(not body.is_empty(), "'%s' preview reports body surfaces" % id) # A preview that is not ANIMATING is a preview of the bind pose, which # is the one pose the character will never be in during play. The clip # NAME is not evidence of that — it is a variable this class sets on # itself, and it reads "Idle" just as happily when the animation tree # is not ticking at all. So watch the skeleton move. _check(model.current_clip_debug() == "Idle", "'%s' preview selected Idle (got '%s')" % [id, model.current_clip_debug()]) _check(await _pose_moves(model), "'%s' preview skeleton is actually animating" % id) _check(seen_glb > 0, "shipping GLB character previews were built (%d)" % seen_glb) menu._show_main_menu() await process_frame _check(not menu.loadout_editor.visible, "Back closes the loadout editor") _check(menu.main_vbox.visible, "Pause list is showing again") _done() ## Does the skeleton's pose change over a handful of frames? ## ## Sampled from INSIDE the modifier pass would be better, but the question here ## is only "is anything driving this at all", and for that the animated pose is ## the right thing to read: if the AnimationTree is not ticking, every bone ## holds still and this returns false. func _pose_moves(model) -> bool: var skel: Skeleton3D = model.skeleton if skel == null or skel.get_bone_count() == 0: return false var before: Array = [] for b in skel.get_bone_count(): before.append(skel.get_bone_pose_rotation(b)) for _i in 12: await process_frame for b in skel.get_bone_count(): if not skel.get_bone_pose_rotation(b).is_equal_approx(before[b]): return true return false func _check(ok: bool, what: String) -> void: if ok: print(" OK: %s" % what) else: print(" FAIL: %s" % what) _fails += 1 func _done() -> void: print("\n=== LOADOUT CHARACTER SELECTOR ===\nFailures: %d" % _fails) quit(1 if _fails > 0 else 0)