Skip to content

perf(fp): run skeleton modifiers on the idle frame, not per physics frame - #123

Merged
KunalSiyag merged 1 commit into
mainfrom
droid/r4-modifier-idle
Oct 28, 2026
Merged

KunalSiyag merged 1 commit into
mainfrom
droid/r4-modifier-idle

Conversation

@KunalSiyag

Copy link
Copy Markdown
Owner

What

Every Skeleton3D defaults to modifier_callback_mode_process = PHYSICS (Godot 4.7), so the game's GDScript SkeletonModifier3Ds — DogRig/CatRig on every animal body, the hero's strike pose and head look, the T0 conversation head look — ran once per physics frame at 60 tps. The fp layer animates entirely on the idle frame (all mixers are ANIMATION_CALLBACK_MODE_PROCESS_MANUAL, advanced by hand), and the physics server is parked on the web (#89) — but parked physics frames still tick, so the modifiers kept paying per-tick, and were paid again for every catch-up step a slow frame absorbs (up to 2 on web after #84). The day-10 tick-frame attribution puts ~11 ms of the ~45 ms street_tick p50 in exactly this line (round4/web-perf-budget v3.1).

The fix is one line per attach site: modifier_callback_mode_process = IDLE — modifiers run on the idle frame with the hand-advanced mixers, exactly once per rendered frame, immune to catch-up multiplication. Modifiers use real deltas (dog wag pace, LookAt duration), so behavior is unchanged beyond sub-frame timing.

Attach sites

  • traffic/people_pool.gd — every people body at build (the T0 LookAtModifier3D head look)
  • animals/dog_bodies.gd — DogRig
  • animals/cat_bodies.gd — CatRig
  • player/hero_strike_pose.gd — StrikePose
  • player/hero_look.gd — hero head/neck LookAtModifier3Ds

Tests

  • New fp_kit.check_modifier_modes(fails, root): sweeps the scene for any SkeletonModifier3D and asserts its skeleton is on the IDLE mode — a future physics-callback modifier fails the suite. Wired into fp_animals (12 modifiers swept), fp_cats (16), fp_player (10).
  • Targeted pins: DogRig (fp_animals), CatRig (fp_cats), a seated people body (fp_people_prewarm — its skeleton carries the mode even though seated drivers hold no look modifier), the hero skeleton (fp_player).

FP_ANIMALS_TEST, FP_CATS_TEST, FP_PEOPLE_PREWARM_TEST, FP_PLAYER_TEST all PASS; tools/gate.sh quick and pre-commit green.

Perf

Native bracket measurement (fixed-fps 60, parked server): 48 modifier-bearing skeletons cost 0.275 ms/physics-frame vs 0.172 ms bare — the modifier share is real and scales with characters on screen; on wasm (GDScript callbacks ~5-10× slower) at day-10 counts this is the ~5-6 ms/physics-step line the attribution measured, double-paid on catch-up frames.

…rame

Every Skeleton3D defaults to modifier_callback_mode_process=PHYSICS, so the
GDScript modifiers (DogRig/CatRig on every animal body, the hero's strike pose
and head look, the T0 conversation look) ran once per physics frame and were
paid again for every physics catch-up step a slow web frame absorbs. The fp
animation mixers are all hand-advanced on the idle frame already; run the
modifiers there too (IDLE), so a rendered frame pays them exactly once.
Pinned by a mode assert in the animals, cats, people-prewarm and player suites.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Automated review

No new findings on the 10 changed files (gdlint, complexity, TODO policy, dead code).

Run python3 tools/checks/run_all.py locally for the full set of checks.

@KunalSiyag
KunalSiyag merged commit 1a4afa6 into main Oct 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant