Character and movement pass
NPC characters - Articulated rig (hips/knees, shoulders/elbows/hands) replaces rigid capsules; the walk cycle bends knees and elbows and flees with a longer stride. - Layered outfits (jacket/vest/coat/dress), extended accessories and hand items (bag, umbrella, phone, camera, clipboard, cup), face details and five hair silhouettes, all deterministic per profile seed. - Idle behaviour layers (phone, clipboard, crossed arms, look-around), breathing and weight shift; escalation raises hands or points, and noticed NPCs turn toward the guard. - Reaction symbols above the head (!!, ?!, !, ?), door-queue offsets assigned by GameManager, three LOD tiers with hysteresis and a billboard impostor. - smoke_npc_budget.gd: 16 avatars = 534 meshes, ~6.9 ms average (50 ms budget). Player movement - Separate accel/decel, step-up assist for kerbs and the entrance steps, and a forward-axis fix: W was inverted (get_axis(forward, back) is negative). - Head bob, landing dip and precision-walk FOV, gated by persisted head-bob and invert-Y settings; precision chip and tick; surface-aware footsteps and a landing cue forwarded to AudioFeedback. - Gamepad support (sticks, deadzone, face buttons, bumpers), a control legend that follows the active device, and named actions for the remaining keys. - Key/button remapping in Settings, persisted as input_bindings and applied by InputBindings (mouse and gamepad axes are never removed). - smoke_movement.gd (windowed): climbs the steps, checks precision speed, chip, footsteps and landing; new 11-precision-walk capture and catalogue entry. Verification: headless 208/208, Python tooling 56/56, all 11 smokes pass, compare 11 good / 0 bad, movement judge match. Version 0.5.0. Only optional Future ideas remain in TODO.
This commit is contained in:
1 parent
2a4e0d1998
commit
a27e8cb74c
35 files changed
+1608
-185
No files matched your search
@@ -0,0 +1,117 @@
|
||||
extends SceneTree
|
||||
## Movement smoke (windowed): walks the guard to the entrance to check step-up,
|
||||
## measures precision-walk speed, and checks footstep/landing cues. The guard
|
||||
## reads captured-mouse input, so run it windowed:
|
||||
##
|
||||
## godot --path . --script res://src/test/smoke_movement.gd
|
||||
##
|
||||
## Exit code 0 = pass, 1 = failure.
|
||||
|
||||
const MAX_FRAMES := 1600
|
||||
var frame := 0
|
||||
var gm: Node = null
|
||||
var started := false
|
||||
var step := 0
|
||||
var step_frame := 0
|
||||
var failures: Array[String] = []
|
||||
var start_pos := Vector3.ZERO
|
||||
var normal_distance := 0.0
|
||||
var footsteps_before := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
root.size = Vector2i(1280, 720)
|
||||
root.add_child(load("res://scenes/main.tscn").instantiate())
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
frame += 1
|
||||
if gm == null:
|
||||
gm = root.find_child("GameManager", true, false)
|
||||
return false
|
||||
if not started:
|
||||
gm.call("_on_venue_selected", "hollywood_building")
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
started = true
|
||||
return false
|
||||
if frame > MAX_FRAMES:
|
||||
_fail("timeout before the movement checks finished")
|
||||
return true
|
||||
step_frame += 1
|
||||
match step:
|
||||
0:
|
||||
if step_frame < 20:
|
||||
return false
|
||||
_place_guard(8.5)
|
||||
footsteps_before = gm.audio.footsteps
|
||||
start_pos = gm.guard.global_position
|
||||
Input.action_press("move_forward")
|
||||
step = 1
|
||||
step_frame = 0
|
||||
1:
|
||||
if step_frame >= 280:
|
||||
Input.action_release("move_forward")
|
||||
var moved := start_pos.distance_to(gm.guard.global_position)
|
||||
_check(moved > 2.0, "guard walked toward the entrance (%.1f m)" % moved)
|
||||
_check(gm.guard.global_position.y > 0.35,
|
||||
"guard climbed the steps (y=%.2f)" % gm.guard.global_position.y)
|
||||
_check(gm.guard.global_position.z < 4.2,
|
||||
"guard reached the steps (z=%.1f)" % gm.guard.global_position.z)
|
||||
_check(gm.audio.footsteps > footsteps_before,
|
||||
"footsteps are emitted while walking")
|
||||
step = 2
|
||||
step_frame = 0
|
||||
2:
|
||||
if step_frame < 45:
|
||||
return false
|
||||
_place_guard(8.5)
|
||||
normal_distance = 0.0
|
||||
start_pos = gm.guard.global_position
|
||||
Input.action_press("move_forward")
|
||||
Input.action_press("walk_slow")
|
||||
step = 3
|
||||
step_frame = 0
|
||||
3:
|
||||
if step_frame >= 120:
|
||||
Input.action_release("walk_slow")
|
||||
Input.action_release("move_forward")
|
||||
var moved := start_pos.distance_to(gm.guard.global_position)
|
||||
_check(moved > 1.0 and moved < 4.6,
|
||||
"precision walk covers less ground (%.1f m in 2 s)" % moved)
|
||||
var chip = gm.hud.precision_label
|
||||
_check(chip != null and chip.visible,
|
||||
"precision walk shows its HUD chip")
|
||||
step = 4
|
||||
step_frame = 0
|
||||
4:
|
||||
if gm.audio.landings == 0:
|
||||
gm.guard.global_position += Vector3(0, 1.2, 0)
|
||||
if step_frame >= 90:
|
||||
_check(gm.audio.landings > 0, "landing cue fires after a drop")
|
||||
_finish()
|
||||
return true
|
||||
return false
|
||||
|
||||
func _place_guard(z: float) -> void:
|
||||
gm.guard.global_position = Vector3(0, 0.1, z)
|
||||
gm.guard.rotation.y = 0.0
|
||||
gm.guard.velocity = Vector3.ZERO
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if condition:
|
||||
print("ok: " + message)
|
||||
else:
|
||||
_fail(message)
|
||||
|
||||
func _fail(message: String) -> void:
|
||||
failures.append(message)
|
||||
_finish()
|
||||
|
||||
func _finish() -> void:
|
||||
Input.action_release("move_forward")
|
||||
Input.action_release("walk_slow")
|
||||
if failures.is_empty():
|
||||
print("SMOKE OK: movement feel (steps, precision walk, footsteps)")
|
||||
quit(0)
|
||||
else:
|
||||
for message in failures:
|
||||
printerr("SMOKE FAIL: " + message)
|
||||
quit(1)
|
||||
Reference in new issue
Block a user