fix: clean state_ground and ensure duck-type state machine bootstrap
This commit is contained in:
+14
-15
@@ -37,20 +37,20 @@ func _box_static(pos: Vector3, size: Vector3, color: Color) -> StaticBody3D:
|
||||
|
||||
func _build_input_map() -> void:
|
||||
var bindings := {
|
||||
jump = [KEY_SPACE],
|
||||
sprint = [KEY_SHIFT],
|
||||
crouch = [KEY_CTRL],
|
||||
dash = [KEY_SHIFT],
|
||||
fire = [MOUSE_BUTTON_RIGHT, KEY_F],
|
||||
fire_alt = [MOUSE_BUTTON_LEFT],
|
||||
reload = [KEY_R],
|
||||
interact = [KEY_E],
|
||||
move_forward = [KEY_W],
|
||||
move_back = [KEY_S],
|
||||
move_left = [KEY_A],
|
||||
move_right = [KEY_D],
|
||||
weapon_next = [MOUSE_BUTTON_WHEEL_UP],
|
||||
weapon_prev = [MOUSE_BUTTON_WHEEL_DOWN],
|
||||
"jump": [KEY_SPACE],
|
||||
"sprint": [KEY_SHIFT],
|
||||
"crouch": [KEY_CTRL],
|
||||
"dash": [KEY_SHIFT],
|
||||
"fire": [MOUSE_BUTTON_RIGHT, KEY_F],
|
||||
"fire_alt": [MOUSE_BUTTON_LEFT],
|
||||
"reload": [KEY_R],
|
||||
"interact": [KEY_E],
|
||||
"move_forward": [KEY_W],
|
||||
"move_back": [KEY_S],
|
||||
"move_left": [KEY_A],
|
||||
"move_right": [KEY_D],
|
||||
"weapon_next": [MOUSE_BUTTON_WHEEL_UP],
|
||||
"weapon_prev": [MOUSE_BUTTON_WHEEL_DOWN],
|
||||
}
|
||||
for action in bindings:
|
||||
if not InputMap.has_action(action):
|
||||
@@ -136,7 +136,6 @@ func _build_player() -> void:
|
||||
player.add_child(camera)
|
||||
|
||||
# set_script() does NOT call _ready(); bootstrap manually after children are in place
|
||||
# IMPORTANT: player._ready() MUST run BEFORE sm._ready() so the machine has valid params
|
||||
if player.has_method("_ready"):
|
||||
player._ready()
|
||||
if sm.has_method("_ready"):
|
||||
|
||||
@@ -12,10 +12,11 @@ func _ensure_machine() -> MovementStateMachine:
|
||||
if is_instance_valid(_machine):
|
||||
return _machine
|
||||
if has_node("MovementStateMachine"):
|
||||
_machine = $MovementStateMachine
|
||||
if _machine is MovementStateMachine:
|
||||
var node = $MovementStateMachine
|
||||
if node and node.has_method("switch_to") and node.has_method("register_chain_mechanic"):
|
||||
_machine = node
|
||||
return _machine
|
||||
_machine = null
|
||||
_machine = null
|
||||
return null
|
||||
|
||||
|
||||
|
||||
@@ -15,11 +15,7 @@ func exit() -> void:
|
||||
|
||||
|
||||
func update(delta: float) -> void:
|
||||
var sm := machine
|
||||
if sm and sm.player:
|
||||
pass
|
||||
|
||||
var vel: Vector3 = sm.player.velocity # assumed present in debug
|
||||
var vel: Vector3 = machine.player.velocity
|
||||
|
||||
# Input direction
|
||||
var input_v := machine.input_dir
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
extends Node
|
||||
class_name MovementStateMachineTest
|
||||
|
||||
## Tests for MovementStateMachine logic.
|
||||
## Run: godot --headless --path . --script movement/tests/test_fsm_runner.gd --quit
|
||||
|
||||
var sm: MovementStateMachine
|
||||
var fake_player: CharacterBody3D
|
||||
var params: MovementParams
|
||||
@@ -19,6 +16,7 @@ func run_all() -> void:
|
||||
tests_failed = 0
|
||||
params = MovementParams.new()
|
||||
fake_player = CharacterBody3D.new()
|
||||
fake_player.set_floor_max_angle(0.01)
|
||||
sm = MovementStateMachine.new()
|
||||
sm.player = fake_player
|
||||
sm.params = params
|
||||
@@ -68,15 +66,14 @@ func _fresh() -> void:
|
||||
sm.current_jump_count = 0
|
||||
sm.wall_normal = Vector3.ZERO
|
||||
sm.input_dir = Vector2.ZERO
|
||||
sm.input_jump_pressed = false
|
||||
sm.input_jump_just_pressed = false
|
||||
sm.input_jump_pressed = false
|
||||
sm.input_sprint = false
|
||||
sm.input_crouch = false
|
||||
sm.input_dash = false
|
||||
fake_player.velocity = Vector3.ZERO
|
||||
fake_player.set_floor_max_angle(0.01)
|
||||
for c in sm.get_children():
|
||||
sm.remove_child(c)
|
||||
sm.remove_child(c.queue_free())
|
||||
|
||||
|
||||
func _expect(cond: bool, msg: String) -> void:
|
||||
@@ -92,46 +89,70 @@ func _eq(a: Variant, b: Variant, msg: String = "") -> void:
|
||||
# ── tests ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func test_states_initialize_with_ground() -> void:
|
||||
var g = Node.new(); g.name = "state_ground"; g.machine = sm; sm.add_child(g)
|
||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
||||
var g = Node.new()
|
||||
g.name = "state_ground"
|
||||
g.machine = sm
|
||||
sm.add_child(g)
|
||||
var a = Node.new()
|
||||
a.name = "state_air"
|
||||
a.machine = sm
|
||||
sm.add_child(a)
|
||||
sm.switch_to("ground")
|
||||
_eq(sm.current_state, "ground")
|
||||
|
||||
|
||||
func test_ground_transitions_to_air_on_jump() -> void:
|
||||
var g = Node.new(); g.name = "state_ground"; g.machine = sm; sm.add_child(g)
|
||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
||||
var g = Node.new()
|
||||
g.name = "state_ground"
|
||||
g.machine = sm
|
||||
sm.add_child(g)
|
||||
var a = Node.new()
|
||||
a.name = "state_air"
|
||||
a.machine = sm
|
||||
sm.add_child(a)
|
||||
sm.on_ground = true
|
||||
sm.switch_to("air")
|
||||
_eq(sm.current_state, "air")
|
||||
|
||||
|
||||
func test_air_transitions_to_ground_on_land() -> void:
|
||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
||||
var a = Node.new()
|
||||
a.name = "state_air"
|
||||
a.machine = sm
|
||||
sm.add_child(a)
|
||||
sm.switch_to("ground")
|
||||
sm.on_ground = false
|
||||
fake_player.velocity = Vector3.ZERO
|
||||
fake_player.set_floor_max_angle(0.01)
|
||||
sm.switch_to("air")
|
||||
fake_player.velocity = Vector3.ZERO
|
||||
fake_player.set_floor_max_angle(0.01)
|
||||
sm.switch_to("ground")
|
||||
_eq(sm.current_state, "ground")
|
||||
|
||||
|
||||
func test_double_jump_allowed_once() -> void:
|
||||
params.double_jump_max_count = 1
|
||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
||||
var a = Node.new()
|
||||
a.name = "state_air"
|
||||
a.machine = sm
|
||||
sm.add_child(a)
|
||||
sm.on_ground = false
|
||||
sm.current_jump_count = 1
|
||||
_eq(sm.current_jump_count, 1)
|
||||
_expect(sm.current_jump_count < params.double_jump_max_count + 1,
|
||||
"should allow double jump once")
|
||||
_expect(
|
||||
sm.current_jump_count < params.double_jump_max_count + 1,
|
||||
"should allow double jump once"
|
||||
)
|
||||
|
||||
|
||||
func test_wall_run_started_when_near_wall() -> void:
|
||||
var g = Node.new(); g.name = "state_ground"; g.machine = sm; sm.add_child(g)
|
||||
var a = Node.new(); a.name = "state_air"; a.machine = sm; sm.add_child(a)
|
||||
var g = Node.new()
|
||||
g.name = "state_ground"
|
||||
g.machine = sm
|
||||
sm.add_child(g)
|
||||
var a = Node.new()
|
||||
a.name = "state_air"
|
||||
a.machine = sm
|
||||
sm.add_child(a)
|
||||
sm.wall_normal = Vector3.RIGHT.normalized()
|
||||
sm.switch_to("wall_run")
|
||||
_eq(sm.current_state, "wall_run")
|
||||
@@ -144,26 +165,31 @@ func test_chain_bonus_caps_at_50pct() -> void:
|
||||
sm.register_chain_mechanic("slide")
|
||||
sm.register_chain_mechanic("wall_run")
|
||||
_expect(sm.chain_count == 3, "chain_count 3 after 3 mechanics")
|
||||
_expect(sm.current_chain_bonus <= params.chain_bonus_cap + 0.001,
|
||||
"bonus should be bounded by cap")
|
||||
_expect(
|
||||
sm.current_chain_bonus <= params.chain_bonus_cap + 0.001,
|
||||
"bonus should be bounded by cap"
|
||||
)
|
||||
|
||||
|
||||
func test_sliding_reduces_speed() -> void:
|
||||
params.slide_speed = 14.0
|
||||
params.slide_min_speed = 12.0
|
||||
_expect(params.slide_min_speed < params.slide_speed,
|
||||
"slide_min_speed must be below slide_speed")
|
||||
_expect(params.slide_min_speed < params.slide_speed, "slide_min_speed must be below slide_speed")
|
||||
|
||||
|
||||
func test_dash_speed_under_effective_cap() -> void:
|
||||
var d = Node.new(); d.name = "state_dash"; d.machine = sm; sm.add_child(d)
|
||||
var d = Node.new()
|
||||
d.name = "state_dash"
|
||||
d.machine = sm
|
||||
sm.add_child(d)
|
||||
sm.on_ground = false
|
||||
var eff := sm.get_effective_speed(params.dash_speed)
|
||||
_expect(eff <= params.dash_speed * (1.0 + params.chain_bonus_cap),
|
||||
"dash effective speed should respect chain cap")
|
||||
_expect(
|
||||
eff <= params.dash_speed * (1.0 + params.chain_bonus_cap),
|
||||
"dash effective speed should respect chain cap"
|
||||
)
|
||||
|
||||
|
||||
func test_rocket_jump_impulse_sets_upward_velocity() -> void:
|
||||
# Unit test: verify impulse formula in state_machine
|
||||
params.rocket_jump_up_impulse = 20.0
|
||||
_expect(params.rocket_jump_up_impulse > 0.0, "rocket jump must have positive upward impulse")
|
||||
|
||||
Reference in New Issue
Block a user