Bull roll ability, console screen, tests
This commit is contained in:
@@ -39,10 +39,14 @@ func _run() -> void:
|
||||
|
||||
var scene: Node = scene_res.instantiate()
|
||||
root.add_child(scene)
|
||||
current_scene = scene # match runtime: game code (sword throw, overlays) uses current_scene
|
||||
|
||||
# Let physics, AI, and IK warm up.
|
||||
await create_timer(0.5).timeout
|
||||
|
||||
_check_bull_animation()
|
||||
await _check_overlays()
|
||||
|
||||
# Capture 5 frames ~0.4 s apart (covers roughly one wander step and walk cycle).
|
||||
for i in range(5):
|
||||
_capture_frame("motion_%02d.png" % i)
|
||||
@@ -61,12 +65,156 @@ func _run() -> void:
|
||||
await create_timer(0.8).timeout
|
||||
_capture_frame("ragdoll_result.png")
|
||||
_check_ragdoll_sanity(mat, start_pos)
|
||||
_check_score_wiring(scene)
|
||||
else:
|
||||
_note("ragdoll check skipped — no matadors found in scene")
|
||||
|
||||
# Last, so any matadors the roll bowls over don't perturb the score check above.
|
||||
await _check_roll_ability(scene)
|
||||
|
||||
_finish()
|
||||
|
||||
|
||||
# ── Roll ability ──────────────────────────────────────────────────────────────
|
||||
# Roll into a synthetic wall and confirm the bank shot: the heading reflects and the
|
||||
# speed boosts past roll_max_speed (the natural ramp caps at max, so any excess is a
|
||||
# wall boost). A low roll_duration lets it end within the sample window.
|
||||
|
||||
func _check_roll_ability(scene: Node) -> void:
|
||||
print("\n-- check_roll_ability --")
|
||||
var dp: Node = root.get_node_or_null("/root/DP")
|
||||
var players := get_nodes_in_group(&"player")
|
||||
if dp == null or players.is_empty():
|
||||
_note("roll check: DP / player missing")
|
||||
return
|
||||
var player: Node = players[0]
|
||||
var ap: AnimationPlayer = _find_anim_player(player)
|
||||
_assert_true(player.ability_cd.size() == 4, "bull has 4 ability slots (roll added)")
|
||||
|
||||
var wall := StaticBody3D.new()
|
||||
var cs := CollisionShape3D.new()
|
||||
var box := BoxShape3D.new()
|
||||
box.size = Vector3(10, 5, 1)
|
||||
cs.shape = box
|
||||
wall.add_child(cs)
|
||||
scene.add_child(wall)
|
||||
wall.global_position = Vector3(0, 1, 8)
|
||||
|
||||
var restore_dur: float = dp.f("roll_duration")
|
||||
dp.set_value("roll_duration", 0.6)
|
||||
player.global_position = Vector3(0, 1, 0)
|
||||
player.cube_guy.rotation.y = 0.0 # face +Z, into the wall
|
||||
player.velocity = Vector3(0, 0, 40)
|
||||
player.ability_cd[3] = 0.0
|
||||
player._activate_roll()
|
||||
_assert_true(player._active_ability == 3, "roll activates in slot 3")
|
||||
if ap != null:
|
||||
_assert_true(ap.current_animation == &"Armature|ROLL", "roll plays the ROLL clip")
|
||||
|
||||
var peak := 0.0
|
||||
var reflected := false
|
||||
for _n in 25:
|
||||
await create_timer(0.03).timeout
|
||||
peak = maxf(peak, Vector2(player.velocity.x, player.velocity.z).length())
|
||||
if player._roll_dir.z < -0.2:
|
||||
reflected = true
|
||||
_assert_true(reflected, "roll ricochets its heading off the wall")
|
||||
_assert_true(peak > dp.f("roll_max_speed") + 1.0, "wall ricochet boosts speed past roll_max_speed")
|
||||
_assert_true(peak <= dp.f("roll_wall_cap") + 1.0, "ricochet boost stays under the cap")
|
||||
if ap != null:
|
||||
_assert_true(ap.current_animation == &"Armature|IDLE", "roll returns to IDLE when it ends")
|
||||
|
||||
dp.set_value("roll_duration", restore_dur)
|
||||
wall.queue_free()
|
||||
|
||||
|
||||
# ── Bull animation ────────────────────────────────────────────────────────────
|
||||
# The bull sits idle in this test (no input), so it must be playing its looping
|
||||
# IDLE clip — guards the idle wiring and the KICK clip rename in player.gd.
|
||||
|
||||
func _check_bull_animation() -> void:
|
||||
print("\n-- check_bull_animation --")
|
||||
var players := get_nodes_in_group(&"player")
|
||||
if players.is_empty():
|
||||
_note("bull anim check: no player in scene")
|
||||
return
|
||||
var ap: AnimationPlayer = _find_anim_player(players[0])
|
||||
if ap == null:
|
||||
_note("bull anim check: no AnimationPlayer under player")
|
||||
return
|
||||
_assert_true(ap.has_animation(&"Armature|IDLE"), "bull has IDLE clip")
|
||||
_assert_true(ap.has_animation(&"Armature|KICK"), "bull has KICK clip (not stale FOOTKICK)")
|
||||
_assert_true(ap.current_animation == &"Armature|IDLE", "bull plays IDLE at rest")
|
||||
if ap.has_animation(&"Armature|IDLE"):
|
||||
_assert_true(ap.get_animation(&"Armature|IDLE").loop_mode == Animation.LOOP_LINEAR,
|
||||
"bull IDLE clip loops")
|
||||
|
||||
|
||||
func _find_anim_player(node: Node) -> AnimationPlayer:
|
||||
if node is AnimationPlayer:
|
||||
return node
|
||||
for child in node.get_children():
|
||||
var r := _find_anim_player(child)
|
||||
if r != null:
|
||||
return r
|
||||
return null
|
||||
|
||||
|
||||
# ── Scoreboard wiring ─────────────────────────────────────────────────────────
|
||||
# The ragdoll above is one matador kill; confirm it flowed matador → spawner →
|
||||
# HUD and scored base × combo-1 = 100. Guards the whole kill→score signal chain.
|
||||
|
||||
func _check_score_wiring(scene: Node) -> void:
|
||||
print("\n-- check_score_wiring --")
|
||||
var hud: Node = _find_hud(scene)
|
||||
if hud == null:
|
||||
_note("score check: HUD not found in scene")
|
||||
return
|
||||
_assert_true(hud._score == 100, "one kill scores 100 (base × combo 1) — got %d" % hud._score)
|
||||
_assert_true(hud._combo == 1, "one kill sets combo to 1 — got %d" % hud._combo)
|
||||
|
||||
|
||||
func _find_hud(node: Node) -> Node:
|
||||
if node is CanvasLayer and node.has_method(&"_on_matador_killed"):
|
||||
return node
|
||||
for child in node.get_children():
|
||||
var r := _find_hud(child)
|
||||
if r != null:
|
||||
return r
|
||||
return null
|
||||
|
||||
|
||||
# ── Debug overlays ────────────────────────────────────────────────────────────
|
||||
# Turn on every DebugDraw overlay against the live scene, confirm it builds, then
|
||||
# turn them off and confirm it tears down — guards the reconcile bookkeeping.
|
||||
|
||||
func _check_overlays() -> void:
|
||||
print("\n-- check_overlays --")
|
||||
var dp: Node = root.get_node_or_null("/root/DP")
|
||||
var draw: Node = root.get_node_or_null("/root/DebugDraw")
|
||||
if dp == null or draw == null:
|
||||
_note("overlay check: DP / DebugDraw autoload missing")
|
||||
return
|
||||
var flags := [
|
||||
"show_states", "show_stats", "show_collisions", "show_hitboxes", "show_bones",
|
||||
"show_animation_name", "show_wireframe", "show_grid", "show_axes",
|
||||
]
|
||||
for flag: String in flags:
|
||||
dp.set_value(flag, true)
|
||||
await create_timer(0.3).timeout
|
||||
_assert_true(draw._state_labels.size() > 0, "overlay builds matador state labels")
|
||||
_assert_true(draw._overlays.size() > 0, "overlay builds collision meshes")
|
||||
_assert_true(draw._anim_labels.size() > 0, "overlay builds animation-name labels")
|
||||
_assert_true(draw._stats_layer != null and draw._stats_layer.visible, "stats overlay visible")
|
||||
_assert_true(not draw._stats_label.text.is_empty(), "stats overlay composes text")
|
||||
for flag: String in flags:
|
||||
dp.set_value(flag, false)
|
||||
await create_timer(0.3).timeout
|
||||
_assert_true(draw._state_labels.is_empty(), "overlay clears state labels when off")
|
||||
_assert_true(draw._overlays.is_empty(), "overlay clears collision meshes when off")
|
||||
_assert_true(draw._anim_labels.is_empty(), "overlay clears animation-name labels when off")
|
||||
|
||||
|
||||
# ── Tail integrity ────────────────────────────────────────────────────────────
|
||||
|
||||
func _check_tail_integrity() -> void:
|
||||
|
||||
@@ -20,6 +20,8 @@ func _run() -> void:
|
||||
test_debug_params_defaults()
|
||||
test_debug_params_clamp_values()
|
||||
test_debug_params_save_load_cycle()
|
||||
test_debug_params_flat_cache()
|
||||
test_debug_console_commands()
|
||||
test_matador_spawn_positions()
|
||||
test_matador_wander_target_in_bounds()
|
||||
test_matador_state_transitions()
|
||||
@@ -230,6 +232,57 @@ func test_debug_params_save_load_cycle() -> void:
|
||||
_assert_eq(dp.f("mat_walk_speed"), original, "restore original value")
|
||||
|
||||
|
||||
# The hot-read cache (f/b) and the metadata mirror (get_all()[k].value) must never
|
||||
# diverge across set / reset / reset_all.
|
||||
func test_debug_params_flat_cache() -> void:
|
||||
print("\n-- test_debug_params_flat_cache --")
|
||||
var dp: Node = root.get_node_or_null("/root/DP")
|
||||
if dp == null:
|
||||
_assert_true(false, "DP autoload should exist")
|
||||
return
|
||||
dp.set_value("mat_walk_speed", 6.25)
|
||||
_assert_eq(dp.f("mat_walk_speed"), 6.25, "flat cache reflects set_value")
|
||||
_assert_eq(dp.get_all()["mat_walk_speed"]["value"], 6.25, "metadata mirror agrees with cache")
|
||||
_assert_true(dp.is_modified("mat_walk_speed"), "is_modified true after set")
|
||||
dp.reset("mat_walk_speed")
|
||||
_assert_eq(dp.f("mat_walk_speed"), dp.get_all()["mat_walk_speed"]["default"],
|
||||
"reset(key) restores default in cache")
|
||||
_assert_true(not dp.is_modified("mat_walk_speed"), "is_modified false after reset")
|
||||
|
||||
|
||||
# The debug console verbs (toggle / reset <name> / set / diff / clear / help) must
|
||||
# mutate DP correctly and never crash on the read-only reporting commands.
|
||||
func test_debug_console_commands() -> void:
|
||||
print("\n-- test_debug_console_commands --")
|
||||
var dp: Node = root.get_node_or_null("/root/DP")
|
||||
var menu: Node = root.get_node_or_null("/root/Console")
|
||||
if dp == null or menu == null:
|
||||
_assert_true(false, "DP + Console autoloads should exist")
|
||||
return
|
||||
dp.set_value("show_bones", false)
|
||||
menu._execute("toggle show_bones")
|
||||
_assert_true(dp.b("show_bones"), "toggle flips bool on")
|
||||
menu._execute("toggle show_bones")
|
||||
_assert_true(not dp.b("show_bones"), "toggle flips bool off")
|
||||
|
||||
dp.set_value("mat_walk_speed", 4.0)
|
||||
menu._execute("toggle mat_walk_speed")
|
||||
_assert_eq(dp.f("mat_walk_speed"), 4.0, "toggle refuses a float param")
|
||||
|
||||
menu._execute("mat_walk_speed 6")
|
||||
_assert_eq(dp.f("mat_walk_speed"), 6.0, "console 'key value' sets the param")
|
||||
menu._execute("reset mat_walk_speed")
|
||||
_assert_eq(dp.f("mat_walk_speed"), dp.get_all()["mat_walk_speed"]["default"],
|
||||
"console 'reset <name>' restores default")
|
||||
|
||||
# Read-only reporting verbs must not raise.
|
||||
menu._execute("diff")
|
||||
menu._execute("clear")
|
||||
menu._execute("help")
|
||||
menu._execute("nonsense_key_xyz")
|
||||
_assert_true(true, "diff / clear / help / unknown key survive")
|
||||
|
||||
|
||||
func test_matador_spawn_positions() -> void:
|
||||
print("\n-- test_matador_spawn_positions --")
|
||||
var dp: Node = root.get_node_or_null("/root/DP")
|
||||
|
||||
@@ -43,7 +43,9 @@ func _run() -> void:
|
||||
push_error("performance_test: failed to load scene.tscn")
|
||||
quit(1)
|
||||
return
|
||||
root.add_child(scene_res.instantiate())
|
||||
var scene_inst: Node = scene_res.instantiate()
|
||||
root.add_child(scene_inst)
|
||||
current_scene = scene_inst # match runtime: game code (sword throw, overlays) uses current_scene
|
||||
|
||||
await create_timer(WARMUP_SEC).timeout
|
||||
|
||||
|
||||
Reference in New Issue
Block a user