Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/fp/animals/cat_bodies.gd
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ func _bind(b: DB.Body, scene: Node3D) -> void:
b.head = b.skel.find_bone("Head")
var rig := CAT_RIG.new()
rig.name = "CatRig"
# The rig runs on the idle frame, not once per physics frame (people_pool.gd says why).
b.skel.modifier_callback_mode_process = Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
b.skel.add_child(rig)
rig.bind(b.skel, scene, CAT_RIG.CAT_TAIL, "Neck")
b.rig = rig
Expand Down
2 changes: 2 additions & 0 deletions scripts/fp/animals/dog_bodies.gd
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ func _bind(b: Body, scene: Node3D) -> void:
b.head = b.skel.find_bone("Head")
b.rig = RIG.new()
b.rig.name = "DogRig"
# The rig runs on the idle frame, not once per physics frame (people_pool.gd says why).
b.skel.modifier_callback_mode_process = Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
b.skel.add_child(b.rig)
b.rig.bind(b.skel, scene)

Expand Down
2 changes: 2 additions & 0 deletions scripts/fp/player/hero_look.gd
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ func _modifier(n: String, bone: String, yaw_lim: float, pitch_lim: float) -> Loo
m.transition_type = Tween.TRANS_SINE
m.ease_type = Tween.EASE_IN_OUT
m.origin_safe_margin = LOOK_SAFE_MARGIN
# Runs on the idle frame, not once per physics frame (people_pool.gd says why).
_skeleton.modifier_callback_mode_process = Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
_skeleton.add_child(m)
return m

Expand Down
3 changes: 3 additions & 0 deletions scripts/fp/player/hero_strike_pose.gd
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ func _init(
_curl.append_array(fist_joints(p_sk, s))
_mod = Mod.new(self)
_mod.name = "StrikePose"
# Runs on the idle frame, after the hand-advanced tree, not once per physics frame
# (the web build's catch-up steps would multiply that; people_pool.gd says more).
p_sk.modifier_callback_mode_process = Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
p_sk.add_child(_mod)
_mod.active = false

Expand Down
4 changes: 4 additions & 0 deletions scripts/fp/traffic/people_pool.gd
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ func make(ai: int) -> Body:
b.root.queue_free()
return null
b.head = b.skel.find_bone("Head")
# Skeleton modifiers (the T0 head look) run on the idle frame with the hand-advanced
# mixers; the engine default PHYSICS mode would pay them once per physics frame,
# multiplied by the web build's catch-up steps on slow frames.
b.skel.modifier_callback_mode_process = Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
for n: Node in b.scene.find_children("*", "GeometryInstance3D", true, false):
b.meshes.append(n as GeometryInstance3D)
var lib: AnimationLibrary = ppl._libs[a.gender]
Expand Down
12 changes: 12 additions & 0 deletions tools/tests/fp_animals_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,18 @@ func _check_lead() -> WK.Walker:
rigged and b.ap.has_animation(&"Gallop") and b.ap.has_animation(&"Attack"),
"lead: the dog is the rigged %s with its clips and a DogRig" % d.breed
)
K.check(
fails,
(
b.skel != null
and (
b.skel.modifier_callback_mode_process
== Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
)
),
"lead: the DogRig runs on the idle frame, not once per physics frame"
)
K.check_modifier_modes(fails, get_root())
var worst := 0.0
var left := 0.0
var n := 0
Expand Down
12 changes: 12 additions & 0 deletions tools/tests/fp_cats_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,18 @@ func _check_spawn() -> LIFE.Cat:
dressed and clips and c.body.rig is CB.CAT_RIG and not c.name.is_empty(),
"spawn: %s, a %s %s cat in the cat's body and its coat" % [c.name, c.temper, c.coat]
)
K.check(
fails,
(
c.body.skel != null
and (
c.body.skel.modifier_callback_mode_process
== Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
)
),
"spawn: the CatRig runs on the idle frame, not once per physics frame"
)
K.check_modifier_modes(fails, get_root())
cats.scan(ctx.player.position, false, true, {})
var gone := _cat() == null
_step(AF.SCAN_S + 0.1)
Expand Down
21 changes: 21 additions & 0 deletions tools/tests/fp_kit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,27 @@ static func check(fails: Array, ok: bool, what: String) -> void:
fails.append(what)


## Every SkeletonModifier3D under root rides a skeleton in the IDLE modifier callback mode:
## the engine default PHYSICS mode pays each modifier once per physics frame, multiplied by
## the web build's catch-up steps on slow frames (the day-10 tick-frame attribution).
static func check_modifier_modes(fails: Array, root: Node) -> void:
var mods := root.find_children("*", "SkeletonModifier3D", true, false)
var bad := 0
for m: Node in mods:
var sk := m.get_parent() as Skeleton3D
var idle := (
sk != null
and sk.modifier_callback_mode_process == Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
)
if not idle:
bad += 1
check(
fails,
bad == 0,
"every SkeletonModifier3D runs on the idle frame (%d of %d off)" % [bad, mods.size()]
)


## Input events for a feature's _unhandled_input: action a pressed.
static func action_event(a: String) -> InputEventAction:
var e := InputEventAction.new()
Expand Down
13 changes: 13 additions & 0 deletions tools/tests/fp_people_prewarm_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,17 @@ func _init() -> void:
pe.get("scene_loads") == settled,
"a seat after warming initiates no load (still %d)" % settled
)

# The body a seat lends runs its skeleton modifiers (the T0 head look) on the idle frame
# with the hand-advanced mixers, not once per physics frame (web catch-up multiplies that).
var seated: Array = (pe.get("_pool") as RefCounted).get("_seated")
var sk := (seated[0].get("skel") if not seated.is_empty() else null) as Skeleton3D
K.check(
fails,
(
sk != null
and sk.modifier_callback_mode_process == Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
),
"a seated body's modifiers run on the idle frame, not once per physics frame"
)
K.finish(self, "FP_PEOPLE_PREWARM_TEST", fails)
18 changes: 18 additions & 0 deletions tools/tests/fp_player_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func _init() -> void:
_check_thin_wall(ctx)
_check_stuck(ctx)
_check_cam_trunk(ctx)
_check_hero_modifier_mode(ctx)
K.finish(self, "FP_PLAYER_TEST", fails)


Expand Down Expand Up @@ -1486,3 +1487,20 @@ func _check_cam_trunk(ctx: FpContext) -> void:
"a trunk where the camera sat: inside it or looking through it on %d of 240 frames" % bad
)
K.check(fails, vaultable.is_empty(), "parkour's boxes_near still leaves the trunk out")


## The hero's strike-pose and head-look modifiers run on the idle frame, after the
## hand-advanced AnimationTree, not once per physics frame (the web build's catch-up steps
## would multiply that).
func _check_hero_modifier_mode(ctx: FpContext) -> void:
var rig: Variant = (ctx.player as PL).avatar.get("rig")
var sk := (rig.get("skeleton") if rig != null else null) as Skeleton3D
K.check(
fails,
(
sk != null
and sk.modifier_callback_mode_process == Skeleton3D.MODIFIER_CALLBACK_MODE_PROCESS_IDLE
),
"hero: the skeleton's modifiers run on the idle frame, not once per physics frame"
)
K.check_modifier_modes(fails, get_root())