- Joined
- Jan 3, 2022
- Messages
- 364
The essence of this thread, an abstract: April 2022
- The game runs at 50 ticks per second when at 100% speed.
- Timers are correctly affected by set game speed. (TriggerSleepAction aka Wait is not)
- Timers are calculated as part of the game simulation loop and the game speed correctly affects any past and current timer
- Unit attack counter vs 20ms timer counter: they diverged! I don't know if this means that the timer runs more often (more than once per game tick every now and then) -or- that the unit skips attacks (<1 per tick)
- If timers at 20ms don't provide a perfect "once per game turn" clock, then this discussion is valid
- If it's impossible to make the unit attack "once per game turn" or find a similar event source for a clock, then this discussion ends and timers win
- It is practically useless to make timers <20ms (0.020) or any odd value that isn't a multiple of 20ms (40, 60, 80ms...)
- Testing required: at which point in game turn are timers executed? How are they executed if <20ms or odd values?
It strikes me as weird that everywhere in the game API, GUI and code, the only option to handle time is by specifying seconds. Timers are based on seconds, TriggerSleepAction in seconds, PolledWait wants seconds too. I've tried to understand how they work and documented them so far:
Timers: apparently they're as precise as the OS scheduler allows them to be. They're correctly affected by game time
TriggerSleepAction: at best 100ms accuracy, NOT affected by game time (was it changed in Reforged?)
PolledWait: although based on Timers, it keeps waiting until the timer expires using TriggerSleepAction. Thanks to multiple sleeps it achieves worse accuracy and precision than TriggerSleepAction
I arrived here because I'm trying to figure out the root cause of desyncs of a map that I transpiled from Jass to Lua, 1:1. Here's a quote from game's Desync.txt log (%userprofile%\documents)
Here from <COMPUTER_NAME>_mmDDYY_HHMMSS_Desync.log:<Exception.Summary:>
Network desync on turn 00706
<:Exception.Summary>
The game obviously has turns. Each new simulation step is a new turn. So why aren't we tracking time and delays in terms of game's ticks? Is it possible to find out when a new turn has started and emit events, finally using these events as a clock source? Like in the real world, you can have different sources of time: your wall clock or the incredibly precise atomic clocks.[Desync - 1918987876 - Turn(00000706) = 1347594696]
What if the desyncs in Lua are caused by timers' imprecisions? Is it plausible or do timers work differently? (I will open a thread about the map's desyncs elsewhere. to be updated with a link)
In any case, if we avoid "wall clock-based" timers and timeouts then our only and coalesced source of time will be the perfectly synchronized and deterministic game simulation itself. What if we start two 1s timers as it is. Will they always tick one after another, in the order they were started? Idk. Two timers, 2s and 1s? Idk, to be tested. Though by taking a different approach we can avoid "seconds" and this question altogether.
Event source for Game ticks: Unit Attacks
This seemed like a perfect candidate. We make a unit attack ultra-fast, hopefully it attacks every single game tick. With "Unit Attacked" event we then achieve an event that fires every game tick. My research:(2008): 45-46 attacks/s, by @Dagguh
(2017): WC3 runs at an internal frame rate of 1 / 0.03 (~33.3...) frames per game second, by @Dr Super Good
(2017): 45 attacks/s, also Dr Super Good
(2021): Also 45 attacks/s
It seems unlikely that the units would be able to attack more than once per game tick? Therefore I think the game runs at 45 TPS at 1.0x speed.
To achieve this attack speed you need:
- Combat - Attack # - Animation Backswing Point = 0.0
- Combat - Attack # - Animation Damage Point = 0.0
- Combat - Attack # - Cooldown Time = 0.0
- (The test hero also had an astronomical agility value, TBD)
Menu Name | Game constant | Attacks/s | Speed | 10 seconds is: |
High (default) | MAP_SPEED_NORMAL | 45-46 | 1.0x | 10s |
Medium | MAP_SPEED_SLOW | 36-37 | 0.8x | 12.5s |
Slow | MAP_SPEED_SLOWEST | 27-28 | 0.6x | 16.667s |
Thoughts?
This brings me back to the timer discussion: why use timers and seconds when it's possible to connect with game's simulation ticks and rely on them for all calculations and time tracking? Wouldn't it be more accurate and precise, with guaranteed determinism? I haven't yet tested if regular timers and TriggerActionSleep provide all these guarantees and I want to hear your thoughts. Instead of creating/starting new timers, you would add your Action to an event queue, delayed by X gameticks in time.Test map:
Worker training: records wall clock time spent on training the human worker. Set to 10s in WE
OnAttack: Hero and Arrow tower are tracked and written to multiboard. First value is attackCount, second value is attacks/second
Change game speed: -gs <number>
Currently only 0, 1, 2 exist in the game
TriggerSleepAction benchmark: -sleep <seconds>
PolledWait benchmark: -polledwait <seconds>
Wait based on a single Timer (unlike PolledWait): -timerwait <seconds>
OnAttack: Hero and Arrow tower are tracked and written to multiboard. First value is attackCount, second value is attacks/second
Change game speed: -gs <number>
Currently only 0, 1, 2 exist in the game
TriggerSleepAction benchmark: -sleep <seconds>
PolledWait benchmark: -polledwait <seconds>
Wait based on a single Timer (unlike PolledWait): -timerwait <seconds>
PS: Special thanks to @Eikonium for the In-game Lua console. It makes testing and writing documentation much easier.
Attachments
Last edited: