Skip to content

Conversation

@bobtista
Copy link

Closes #2011

Testing (TODO)

Fast drive mode activation via double-click now works consistently at any frame rate and logic time scale setting.

@bobtista bobtista force-pushed the bobtista/fix-particle-uplink-double-click-timing branch 3 times, most recently from bedd3a9 to 4718d05 Compare December 19, 2025 21:41
@bobtista bobtista marked this pull request as ready for review December 19, 2025 22:24
@stephanmeesters
Copy link

I could do a bit of testing and review for this

For both 30hz and 60hz :

  • double click to activate fast beam should feel the same
  • after activating fast beam the trajectory should be the same
  • make a save game right after activating the fast beam then check if the trajectory is the same (for tsh regular and retail compatible versions)
  • load a retail savegame and check that the beam trajectory is the same as in the retail version

Does that seem right?

@bobtista bobtista force-pushed the bobtista/fix-particle-uplink-double-click-timing branch from 4718d05 to fdb318e Compare December 20, 2025 20:26
@Caball009 Caball009 added Gen Relates to Generals ZH Relates to Zero Hour NoRetail This fix or change is not applicable with Retail game compatibility Input Minor Severity: Minor < Major < Critical < Blocker Bug Something is not working right, typically is user facing labels Dec 21, 2025
@bobtista bobtista force-pushed the bobtista/fix-particle-uplink-double-click-timing branch from fdb318e to d3e0024 Compare December 21, 2025 19:53
@bobtista
Copy link
Author

Replicated to Generals in the most recent commit, which differs slightly and is worth giving a quick look

@bobtista bobtista force-pushed the bobtista/fix-particle-uplink-double-click-timing branch 2 times, most recently from 8e53756 to c01dbb2 Compare December 22, 2025 03:58
@stephanmeesters
Copy link

Tested this by making a savegame of a skirmish at max gamespeed, then loaded it back at regular gamespeed. Trajectory of the beam and beam duration appeared the same also using fast drive

Using the compatibility flags I loaded a retail gamesave, and also saved a game and loaded that in retail. That works fine


I thought it would be interesting to test 30hz and 60hz with the same gamespeed the way GO does it, stephanmeesters@7491c32

Double click felt the same, trajectory and duration the same

It did have problems with the replay as the beam duration was no longer the same, but the gametime was also reported wrong, so that's probably just because I hacked that GO code in

Caball009
Caball009 previously approved these changes Jan 4, 2026
Copy link

@Caball009 Caball009 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and works. Looks good to me.

Edit: tested only in skirmish.

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this fix is unsafe.

The safe fix likely is to scale m_doubleClickToFastDriveDelay with LOGICFRAMES_PER_SECOND/BaseFps (and other scales, see FramePacer).

#if RETAIL_COMPATIBLE_XFER_SAVE
const XferVersion currentVersion = 2;
#if RETAIL_COMPATIBLE_CRC || RETAIL_COMPATIBLE_XFER_SAVE
const XferVersion currentVersion = 3;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be 2.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Generals retail compat version is now 2

const Bool useFasterSpeed = m_scriptedWaypointMode || (m_lastDrivingClickFrame - m_2ndLastDrivingClickFrame < data->m_doubleClickToFastDriveDelay);
#else
DEBUG_ASSERTCRASH(m_lastDrivingClickTimeMsec >= m_2ndLastDrivingClickTimeMsec, ("m_lastDrivingClickTimeMsec should always be >= m_2ndLastDrivingClickTimeMsec"));
const Bool useFasterSpeed = m_scriptedWaypointMode || (m_lastDrivingClickTimeMsec - m_2ndLastDrivingClickTimeMsec < data->m_doubleClickToFastDriveDelay);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not test this but just reading the code I expect that this can and will cause mismatch in Multiplayer. Mismatch probably will not happen always, but there is no guarantee that the delta between 2 timeGetTime calls on 2 different machines will always have the same delta between them, because timeGetTime is low-resolution and time itself is relative in the Network.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timeGetTime values are no longer xfer'd. The double-click detection only affects the local player's beam speed selection and doesn't impact game state synchronization, right? Each player's clicks are processed locally before being sent as network commands.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please test with 2 clients in Network match. The clicks are used to determine logic state. If these do not match exactly, then clients will use different laser beam speeds.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I ended up going with frames and scaling the comparison in frame count so it behaves the same.

Copy link

@Caball009 Caball009 Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would work for GO 60 hz, but not for increased logic timescale, right? Maybe getActualLogicTimeScaleOverFpsRatio can help with that.

Copy link
Author

@bobtista bobtista Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm - this comment says "void setLogicTimeScaleFps( Int fps ); ///< Set the logic time scale fps and therefore scale the simulation time. Is capped by the max render fps and does not apply to network matches." and yeah we check

if (TheNetwork != NULL)
      {
          return TheNetwork->getFrameRate(); 

so multiplayer should be just using whatever this is.

Are you talking about playing single player games and manually changing the logic fps? Are you asking about getActualLogicTimeScaleOverFpsRatio vs getActualLogicTimeScaleRatio?

Copy link

@Caball009 Caball009 Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you test the current code in skirmish mode with high logic time scale? Like is there still a 500 msec window to double click at a logic timescale of 4?

Copy link

@xezon xezon Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this would also correct scale with logic time scale. For example set logic fps to 90 in skirmish, simulation will be 3 times as fast, and so double click duration needs to be trippled too I think (you can test it).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I think I've got it right now, and it should work when we speed up logic in skirmish too. I can test soon

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, but this caused CI replay mismatches :(

@bobtista bobtista force-pushed the bobtista/fix-particle-uplink-double-click-timing branch from 871e244 to 99e68e1 Compare January 6, 2026 20:47
@Caball009 Caball009 dismissed their stale review January 7, 2026 18:40

Significant changes since approval.

const UnsignedInt scaledDelay = data->m_doubleClickToFastDriveDelay * LOGICFRAMES_PER_SECOND / BaseFps;
// TheSuperHackers @fix Scale delay threshold by frame rate and logic time scale to maintain consistent real-time feel
UnsignedInt scaledDelay = data->m_doubleClickToFastDriveDelay * LOGICFRAMES_PER_SECOND / BaseFps;
if (TheFramePacer != NULL)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is not expected NULL

if (TheFramePacer != NULL)
{
const Real timeScaleRatio = TheFramePacer->getActualLogicTimeScaleRatio();
scaledDelay = (UnsignedInt)(scaledDelay * timeScaleRatio);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why this causes mismatch. Perhaps check value in headless replay. It is expected 1.

Copy link

@xezon xezon Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we must not scale it this way. It would mean we cannot use time scale to fast forward replays. Perhaps we must accept that double click will be jank then, or have an alternative code path that only works in Single Player and not in Multiplayer & Replay Playback, but this would increase complexity.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking for single player / skirmish is trivial, right? I think I'd rather have that than a poorly working double click.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or how about we make it a proper game message instead of this frame trickery? Won't be Retail compatible, but much cleaner?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a new game message eg MSG_DO_SPECIAL_POWER_OVERRIDE_DESTINATION_FAST
or we could add an argument to the existing one MSG_DO_SPECIAL_POWER_OVERRIDE_DESTINATION

 // In CommandXlat:
  gameMsg->appendLocationArgument(*pos);
  gameMsg->appendIntegerArgument(SPECIAL_INVALID);
  gameMsg->appendObjectIDArgument(INVALID_ID);
  #if !RETAIL_COMPATIBLE_CRC
  gameMsg->appendBoolArgument(useFastDrive);  // NEW
  #endif

I'll run with this - but it does mean we can't use this feature until we break retail compat

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is ok to fix for later. We do not need all fixes for VC6.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, happy to close this if you like. I also took a stab at using MSG_DO_SPECIAL_POWER_OVERRIDE_DESTINATION

@bobtista bobtista force-pushed the bobtista/fix-particle-uplink-double-click-timing branch from 43b4d61 to 31559b2 Compare January 9, 2026 00:37
@bobtista bobtista force-pushed the bobtista/fix-particle-uplink-double-click-timing branch from 31559b2 to 6ea27b9 Compare January 9, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Input Minor Severity: Minor < Major < Critical < Blocker NoRetail This fix or change is not applicable with Retail game compatibility ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ParticleUplinkCannon double-click fast drive timing changes with framerate

4 participants