Skip to content

Scripted camera rotate, zoom and pitch speed changes with framerate #3142

Description

@bobtista

Prerequisites

  • I have searched for similar issues and confirmed this is not a duplicate

Game Version

  • Command & Conquer Generals
  • Command & Conquer Generals: Zero Hour
  • Other (please specify below)

Bug Description

rotateCamera, zoomCamera and pitchCamera convert their authored duration into a count of 33.33 ms frames, then advance exactly one step per render frame. A scripted camera move therefore completes renderFps / logicFps times faster than the mission scripter intended.

m_rcInfo.numFrames = milliseconds/TheW3DFrameLengthInMsec;   // W3DView.cpp:2721
...
void W3DView::rotateCameraOneFrame()
{
	m_rcInfo.curFrame++;                                      // W3DView.cpp:3273

TheW3DFrameLengthInMsec is a fixed 33.33 ms (W3DView.cpp:101), and rotateCameraOneFrame is reached from updateCameraMovements() inside W3DView::update(). That sits on the render path: GameEngine::update() calls TheGameClient->UPDATE() unconditionally on every render frame, which reaches W3DDisplay::draw() and its updateViews(), while TheGameLogic->UPDATE() is gated by canUpdateGameLogic(). Nothing scales the camera step by TheFramePacer. The same applies to zoomCameraOneFrame (:3344) and pitchCameraOneFrame (:3370).

Network games hit this without any special setup, because FramePacer::getActualLogicTimeScaleFps() returns TheNetwork->getFrameRate() whenever TheNetwork is set. The logic rate is then pinned near 30 while the render rate is whatever the player's limit allows, so scripted camera moves in a multiplayer or co-op mission run several times too fast.

Measured with a "1 rotation over 2.00 seconds" camera move, logic time scale pinned at 30 FPS, varying only the max render FPS:

max render FPS wall time vs authored
30 1960 ms correct
60 986 ms 2.0x too fast
120 525 ms 3.8x too fast

numFrames was 60 in every run, so completion time is numFrames / renderFps — linear in the render rate and independent of the logic rate.

Game logic also stays frozen for as long as the render-paced camera takes: GameEngine::isTimeFrozen() waits on isCameraMovementFinished(), whose flags are cleared only by those render-paced functions, and that gates GameLogic::update(). At high frame rates the mission resumes proportionally early.

Camera translation along a waypoint path is already correct, and the fix for it is in the same file: #1451 converted moveAlongWaypointPath to accumulate elapsed milliseconds (m_mcwpInfo.elapsedTimeMilliseconds += milliseconds) fed by TheFramePacer->getLogicTimeStepMilliseconds(FramePacer::IgnoreFrozenTime), and compares against a total duration rather than a frame count. Rotate, zoom and pitch were left on the old frame-counting form.

Reproduction Steps

  1. Make a map script with Rotate Camera Around The Current View Center: 1 rotation over 10.00 seconds, with no freeze-time modifier.
  2. In game, set the max render FPS to 30 with Ctrl + Keypad Plus / Ctrl + Keypad Minus, then set the logic time scale to 30 with Shift + Ctrl + Keypad Plus / Shift + Ctrl + Keypad Minus. Both print their current value on screen.
  3. Trigger the rotation and time it. It takes about 10 seconds.
  4. Raise the max render FPS to 120 with Ctrl + Keypad Plus, leaving the logic time scale at 30, and trigger the rotation again.
  5. The rotation completes in roughly a quarter of the time.

The logic time scale has to stay below the max render FPS for the two rates to differ at all — changeLogicTimeScale disables the time scale as soon as the requested value reaches the render limit, which restores the render-aligned behaviour and hides the bug.

Additional Context

Do not use Camera Modifier: Freeze Time for the comparison. In that path W3DDisplay::draw() runs the entire camera move to completion inside a single call, and TheFramePacer->update() lives outside that loop, so the configured FPS limit does not apply at all and both runs bypass the cap.

Camera Modifier: Set Final Zoom / Set Final Pitch derive their duration from the already-inflated remaining frame count and inherit the speedup.

Possibly related: #1995 (lock up during scripted camera intro/outro) and #2647 (Fast Forward broken in replay during scripted camera).

Metadata

Metadata

Assignees

Labels

BugSomething is not working right, typically is user facingRenderingIs Rendering related

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions