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,54 @@
|
||||
extends RefCounted
|
||||
## Headless tests for movement constants, surface lookup, gamepad bindings, and
|
||||
## the settings that drive the camera feel.
|
||||
|
||||
const GuardController := preload("res://src/game/guard_controller.gd")
|
||||
|
||||
func test_surface_lookup_covers_the_block() -> bool:
|
||||
var guard := GuardController.new()
|
||||
var a := TestUtils.check_eq(guard._surface_at(Vector3(0, 0.2, 2.0)), "stone",
|
||||
"the entrance steps are stone")
|
||||
var b := TestUtils.check_eq(guard._surface_at(Vector3(3, 0.2, 8.0)), "concrete",
|
||||
"the sidewalk is concrete")
|
||||
var c := TestUtils.check_eq(guard._surface_at(Vector3(2, 0.1, 18.0)), "asphalt",
|
||||
"the roadway is asphalt")
|
||||
guard.free()
|
||||
return a and b and c
|
||||
|
||||
func test_step_and_stride_stay_modest() -> bool:
|
||||
var a := TestUtils.check(GuardController.STEP_HEIGHT <= 0.35,
|
||||
"step-up stays at kerb height")
|
||||
var b := TestUtils.check(GuardController.STRIDE_LENGTH >= 0.5
|
||||
and GuardController.STRIDE_LENGTH <= 1.2, "stride stays human-sized")
|
||||
var c := TestUtils.check(GuardController.SLOW_WALK_FACTOR < 1.0
|
||||
and GuardController.SLOW_WALK_FACTOR > 0.3, "precision walk is slower but usable")
|
||||
return a and b and c
|
||||
|
||||
func test_gamepad_bindings_are_added_once() -> bool:
|
||||
GuardController._ensure_gamepad_bindings()
|
||||
GuardController._ensure_gamepad_bindings()
|
||||
var expected := ["look_left", "look_right", "look_up", "look_down",
|
||||
"move_forward", "move_back", "move_left", "move_right"]
|
||||
var problems: Array[String] = []
|
||||
for action in expected:
|
||||
if not InputMap.has_action(action):
|
||||
problems.append("%s missing" % action)
|
||||
continue
|
||||
var motions := 0
|
||||
for event in InputMap.action_get_events(action):
|
||||
if event is InputEventJoypadMotion:
|
||||
motions += 1
|
||||
if motions == 0:
|
||||
problems.append("%s has no joypad axis" % action)
|
||||
if InputMap.action_get_deadzone(action) > 0.25:
|
||||
problems.append("%s deadzone too high" % action)
|
||||
var buttons := {"action_menu": JOY_BUTTON_A, "cancel": JOY_BUTTON_B,
|
||||
"inspect": JOY_BUTTON_X, "walk_slow": JOY_BUTTON_LEFT_SHOULDER}
|
||||
for action in buttons.keys():
|
||||
var found := false
|
||||
for event in InputMap.action_get_events(action):
|
||||
if event is InputEventJoypadButton and event.button_index == buttons[action]:
|
||||
found = true
|
||||
if not found:
|
||||
problems.append("%s has no gamepad button" % action)
|
||||
return TestUtils.check(problems.is_empty(), "gamepad actions bound (%s)" % str(problems))
|
||||
Reference in new issue
Block a user