Rudi Foodi’s game loop is a standardDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/mbeckham4-hub/Rudi-Foodi/llms.txt
Use this file to discover all available pages before exploring further.
requestAnimationFrame loop that computes delta time and routes to either the title-screen updater or the gameplay updater each frame. Everything — movement, animation, collision, AI, and physics — runs inside a single JavaScript thread with no workers and no fixed-step substep logic.
The animate() Function
animate() is the root of all per-frame work. It uses a THREE.Clock to measure real elapsed time and caps the delta at 0.033 seconds (≈ 30 fps minimum) to prevent large tunnelling jumps when the tab is backgrounded:
updateDebris() runs unconditionally every frame — debris launched by the meteor explosion keeps flying even during the ending cinematics.gameStarted is false (title screen), only updateTitle(dt) runs. That function rotates and bobs the preview dog, then calls both titleRenderer.render(titleScene, titleCamera) (the title-card canvas) and renderer.render(scene, camera) (the main scene). Both renderers advance every title-screen frame.
updateGameplay(dt)
updateGameplay is the core simulation step. It handles input → movement → collision → camera every frame.
Input and Movement
Thekeys object is populated by keydown/keyup listeners. The virtual joystick’s normalized joy.x / joy.y are added on top of keyboard input. Both are then rotated into world space based on cameraYaw:
velocity. The velocity is then damped with multiplyScalar(0.88) each frame (friction) and clamped to topSpeed:
rudiPos is a THREE.Vector3 that represents Rudi’s logical world position. The mesh follows it with rudi.position.copy(rudiPos). Boundary clamping at ±720 prevents Rudi from escaping the room walls.
Zoomies
ThezoomLocked boolean is toggled by the ZOOMIES button. When active and boost > 0, topSpeed jumps to 24 * speedMultiplier and the boost meter drains at 22 units/second. It recharges at 18 units/second when Zoomies is off:
Treat and Power-Up Proximity Checks
Collision is distance-based with no broad-phase acceleration — all arrays are iterated every frame. A treat is “collected” when Rudi’s position is within3.1 units:
5 units (they are larger spheres):
Level Advancement
InsidecollectTreat(), after incrementing score, the game checks whether the level goal has been reached. The goal scales with level and caps at 75:
0.25× every level from level 4 onward. An extra Rudi clone is added automatically at level 10.
Camera Follow
The camera orbitsrudiPos each frame using cameraYaw and cameraPitch spherical coordinates at a fixed distance of 20 units. A lerp factor of 0.11 keeps the follow smooth:
Clone AI (updateClones)
Clones do not pathfind — they orbit Rudi in an evenly-spaced ring that rotates slowly over time. Each clone’s target position is computed from a polar offset keyed on its index and performance.now():
collectTreat(treat, true) marks the collection as clone-sourced (used to play a different beep and suppress the spin-mode trigger check).
Fly-Away Clone
When the player accumulates 10 or more speed stacks and at least one clone exists, the first clone is flagged for fly-away:updateClones, fly-away clones skip the orbit logic and instead add flyVelocity * dt to their position each frame, with a positive Y acceleration applied to simulate upward drift:
Debris Physics (updateDebris)
When the meteor explosion triggers explodeRoom(), every mesh in the scene is converted to debris. Each debris object carries vel and spin vectors stored in userData:
updateDebris() applies Euler integration every frame — no collision, no damping, just gravity:
Unlike the task description, the source gravity constant is
0.08 per frame (not 0.25). Debris is never removed from the scene automatically — it is only cleared when the level advances via advanceLevelAfterMeteor() or when the player returns to the menu via resetGameToMenu().Power-Up Animation (updatePowerAnimation)
When Rudi collects a power-up, animateRudiPower(nextForm) sets up a powerAnim object describing a scale transition. updatePowerAnimation(dt) interpolates rudi.scale toward the target each frame:
mid scale (e.g., the “big” squash-stretch) interpolate in two halves: from → mid then mid → to, both using easeOutBack. Animations without a mid scale (e.g., “small” or “normal”) use easeInOut for a single smooth blend.
Each power form also has a style-specific side effect during the tween:
| Form | Style | Side effect |
|---|---|---|
big | growSquash | Intermediate scale wider than final |
small | shrink | rudi.rotation.z wobbles during tween |
speed | stretchZoom | Squashes narrow and long mid-transition |
clone | clonePop | rudi.position.y bounces upward |
normal | settle | Plain lerp back to default scale |
powerAnim.time >= powerAnim.duration, the final scale is snapped to powerAnim.to and powerAnim is set to null.