Showing posts with label Zwift. Show all posts
Showing posts with label Zwift. Show all posts

Sunday, February 7, 2021

Reverse Engineering Zwift Physics - A Fun Look

 INTRO 

Last year, I performed a simple exercise to reverse engineer a cycling ride done on Fulgaz app to understand in-game variables being employed. I described it in this post. It was nice to receive a message from Fulgaz suggesting that I'd come very close to what they actually use for their sanctioned events. 

Overall, the Fulgaz app seemed very in-tune with the physics behavior we cyclists normally expect to encounter with cycling outdoors. Zwift, on the other hand is tricky. None of the in-game physics constants and parameters are known to the public. 

Given many of us outdoor cyclists find the discrepancies between in-game Zwift physics and real world confusing, it is apt to ask whether Zwift is even based on an "earth" model.

With that fun question - which world is Zwift in? - I make an attempt to understand what makes the app work the way it does. Its far from perfect but hopefully it simulates your own thinking and you can go off and try to do something similar. 

Just don't send me any hate mail. My approach maybe far from perfect.


MODELING TOOL

The modeling tool is the same one I used to model the Fulgaz performance. It takes more than 30  variables and allows breaking a course into many many small segments for steady speed analysis. 

For Zwift, I included some adjustments where the model would accept different g constants, drag co-efficient of bikes, altitude de-rates to power depending on whether a cyclist is acclimated or not, segment-by-segment rolling resistances and drag areas depending on the heading of the cyclist, direction of wind and the known characteristics of the road. 

In short, there's a lot of "handles" that I can pull in order to understand the whacky world of Zwift.


METHOD

I rode one lap of Mighty Metropolitan using my real bike hooked up to Computrainer. The in-game bike chosen was the 2021 Canyon Aeroad with Zipp 202 wheelset. My weight and height were set to 64.8 kg and 173cm respectively. The bike weight was assumed to be a race-ready 6.8kg, which came off some forums on the internet. 

Fig 1 : A crazy twisty, windy course! Course details on Veloviewer

Power output was dual recorded. Primary source of power was dual-sided power pedals while Racermate maintained the load at 150W. This would also yield second by second transmission differences between the two sites of power application depending on where on the course I changed a gear or my cadence. Trainer load was held at a constant 150W. I freely chose a cadence.

Using a script for processing the GPX file, the course was broken into 60 segments in order to capture all the features of the terrain - flats, downhills and uphill sections. Specific sections that had the glass or road surfaces were marked for rolling resistance adjustments.

Model variables were tweaked as far as practical to match the model segment time to the Zwift recorded segment time and speeds which came off the raw GPX file. The strategies being :

1) Minimize the difference between average model speed and Zwift reported average speed for the course. 

AND/OR

2) Minimize the difference between segment model speed and Zwift reported segment speed individually for uphills, flats and downhills. 

The exercise seemed to often be a tradeoff between the above two. In principle, the two scenarios should be intertwined but the way I derived segment-segment information from the GPX data could have led to some errors. For example, the average speed in a segment was derived from the data which may have been acutely affected by outliers within that segment. 

The best solution would balance the accuracy in average course speeds with the match within each of the segments. I ran a few scenarios to check the sensitivity of the model. 


ASSUMPTIONS

A CdA of 0.22 sq.m was modeled from frontal area from Bassett et al. (Med Sci Sports Exerc 1999; 31:1665-1676) using my height and weight and co-efficient of drag Cd from Heil was computed as : 

Cd = 4.45 x mass (kg)^-0.45

CdA factors were set to 90% on the flat and downhill sections assuming an aero position but 100% on the uphills. 

Is this CdA representative of the Zwift world? Probably. Earlier, I did some Aero testing using the Chung "regression method" with the same bike and weight settings on another course called Queen's Highway. The CdA that resulted was around 0.21 sq.m (Fig 2). The Crr values were bonkers so instead I tried the Chung "virtual elevation method" and achieved better results (Fig 3). 

In the VE method, the CdA was more like 0.0028 sq.m and Crr around 0.0032. To achieve that, I had to constrain Crr to 0.0032 and used Goal Seek to hone in on the CdA value in order to get the virtual elevation to match the expected elevation profile. Since CdA and Crr have an inverse relationship with each other, I can only find out how sensitive CdA is to a given Crr by changing the Crr value. For this exercise, I simply fixed Crr to 0.0032. 

Fig 2 : (click to view) Results of aero testing done by Chung method. Spreadsheet courtesy of Alex Simmons , Google Wattage Group


Fig 3 : (click to view) Results of the Chung virtual elevation method. Spreadsheet courtesy of Alex Simmons, Google Wattage Group.


From the above results, it appears my simulation tests using CdA between 0.22 and 0.29 sq.m and Crr of 0.003 was alright. However, I'm pretty unsure of modification factors used for the uphill, downhill and flats. Slope is hardly the reason for a rider to change his bike position, infact it must actually be speed that sets the body position. However, I've assumed speed to be low on the uphills to increase the CdA factors and high on the downhills and flats. 

The Crr of tarmac was chosen as 0.003, the same I used for Fulgaz. A Crr of 0.003 is representative of the performance of a high quality tire on a smooth road. Note that the Crr for glassy segments of 65% of tarmac is arbitrary and hypothetical. 

Other factors like acceleration due to gravity, relative humidity, altitude and air temperature in Mighty Metropolitan etc were all based on data from NYC. 


RESULTS

I've attached the results from the tabulation below. 

Fig 4 : (click to view) Tabulation of different case runs with the variables chosen to run the model. The first two runs are representative of earth while the rest are whackier attempts with whimsical variables.


Fig 5 : (click to view) Model performance compared to the "virtual" performance in Zwift for segmented distances of the course using the given variables in Case# 1. 


DISCUSSION

As you can see through Fig 5, the model did well to bring down the overall error in overall course performance time and speed but seemed to struggle with matching Zwift recorded speed and time data for segments. In the best "earth" scenario Case #1 (see Fig 4), the model flew downhills but rode uphills and flats slower. But trying to make segment performance improve had a tradeoff on the course average performance as shown in Case #2. 

The segment specific speed and time matching were not that great. This could also stem from the errors in speed and time calculation within the segments themselves, which in turn stems from irregularities in the original GPX file. Moreover, since the GPX file is a continuous speed run of my avatar with one segment's speed input being linked to the output from a previous segment (such as steep downhill speeds leading to a climb), the transient nature would probably differ from a purely steady state analysis from the model. 

In other whacky attempts (#3-#6), I employed a "non-earth" scenario by manipulating air density and acceleration due to gravity. The best whacky attempt (#6) provided me a near identical error in course average performance speed and time as Scenario #1 but with improvements in the segment specific matching. These results are "whacky" because a change in the acceleration due to gravity and air density should also affect the CdA values, in other words they are all dependent on each other. However, I've ignored that obvious complexity and stopped further attempts here. 


CONCLUSION

I tried to reverse engineer the physics variables and parameters set in the whacky world of Zwift. The model came close but a closer match can be achieved only with inputs of whimsical numbers. A purely steady state type cycling power model is perhaps not best to use for matching to running-segment data however overall, the model course time matched closely with Zwift course time. 

If you don't know what to make of this fun attempt, don't worry. I'm just as puzzled how the world of Zwift works. And perhaps I just said it. The world of Zwift may not even be a world on earth. We would perhaps like to assume so, but it might just be the case that we're in a parallel world as earth with similar names of cities and hypothetical physics. Perhaps those flying cars and glassy climbs where riders seem to have the ability to climb at 40kph and descend at 80kph are enough proof.

As a cyclist who is clearly in-tune with the world around him and how his bike rides in that world for over 15 years, the way Zwift overreports speeds on flats and downhills seems overly flattering. That said, I love my in-game CdA and rolling resistances and whatever other Zwifty physics constants there might be. The enjoyment of Zwifting far overrules the eccentricities of this software.


REFERENCE :

*  *  *

Sunday, August 23, 2020

The Poor Man's Tour de France : Virtual Stage Racing in GT Mimicry

Readers might recall that last year, I attempted a Poor Man's Giro d'Italia, a tongue in cheek name for a stage racing simulation in which the objective was to follow the Giro while riding "short" stages pretty much everyday by myself on local roads. The main motivation behind the exercise was to collect data and compare them to research studies attempted into Grand Tours and Grand Tour racers. 

I'd wanted to replicate something like that this year but with some additional realism to racing. Obviously for this to happen, the intensities would have to be high and I'd have to race with other people. With the whole Covid-19 situation demolishing the race calendar throughout the world, I turned to Zwift for the obvious solution. 

And thereby, I began another self-inflicted stage racing attempt called Poor Man's Tour de France in July. 

I have a few points to make on this mini-adventure before I share the data :

1. The races began on 17th July and lasted upto 21st August. All race results are recorded in my Zwift power user profile. I started Zwift as a beginner in the E/D category and moved up to C by Stage 13. (To download my data in Excel .csv format, you can click on the plot below in Figure 2 where it links to the tabulated data). 

2. All races were done with a single sided pedal based power meter and a heart rate monitor on a non-smart trainer. 

3. The trainer used was the Feedback Sports Omnium Over-drive unit. This is a roller unit which is extremely portable, and perhaps the most portable of all trainers. Owing to direct contact between tire and roller, rolling friction and the dynamics of tire pressure becomes a bit more important than direct-drive units. The unit has minimal inertia. therefore, there is little to no way to coast during racing. If you stop pedaling, you lose power and stop very quickly. On the plus side, riding with this trainer has considerably improved my pedaling conditioning. Due to the direct wheel-on-roller experience, I was also able to get instant audible feedback on stomping vs smooth pedaling patterns.

4. Due to lack of a direct drive setup, I found I had constraints with the inherent power curve available within the above trainer. With the gearing available to me and that power curve, I rarely escalated past cruise powers greater than 200 Watts, for fear of damaging the rollers (I'd already damaged one earlier this year and lost nearly 3 weeks to have a replacement under warranty shipped out to me from Hong Kong!). This also limited the short maximal sprint power outputs I could display within 300 Watts (I was generally not interested in sprinting) 

5. Choice of daily distances were variable. Terrain type was a mix between rolling hill races, mountain stages, few crits and uphill time trials. I skewed the race stages more towards rolling hilly races. 

6. I felt racing every day on Zwift while maneuvering around time constraints as a parent to a 2 year old wasn't really easy. Therefore, I had a few more recovery days in between stages than what would be standard for a Tour. In general, I didn't exceed more than 3 days without a race but the norm was racing every second day as fatigue started accumulating. 

Figure 1 : The author's "pain cave", cobbled together during Covid-19 shelter in place restrictions in UAE. Materials used : book shelf, baby high chair, normal chair, ironing stand, yoga block, a weighing machine, Feedback Omnium Overdrive trainer, road bike, laptop and a 19 inch wide screen monitor. 


Below is the interactive data for all 21 stages of the Poor Man's Tour. Note that data is presented against a logarithmic y-axis to make the plot more readable. Scrolling over the data lines should show data points.


DATA RESULTS


Table 1 : List of races done during 21 day Poor Man's Tour de France on Zwift

Figure 2 : Data for 21 days from a self-inflicted stage racing simulation called The Poor Man's Tour de France (top to bottom) - Total heartbeats, calculated calories, Zwift reported calories, work done, elevation, Trimp points, average heart rate, bike stress, normalized power, average power, average cadence, distance, RPE, TSS/km, TSS/km, & duration. Note that BikeStress is a training metric native to Golden Cheetah which establishes race intensities as a function of duration and intensity. Click on the line to view the data. 


DISCUSSION OF RESULTS

1. Total Distance, Elevation & Calories : Over the total of 21 stages, I completed 600km of racing with a net ascent of 8666m burning an estimated 12000-13000 kcals. This equates to 17% of the actual Tour de France distance with an elevation gain nearly the height of Mt. Everest. These are modest numbers.

2. Heart rate : The range of racing heart rates were between 151-194 bpm across the 21 stages. The highest heart rates were featured in stages with high stochasticity in pacing effort. For example, the two crits I attempted on Stage 9 and Stage 14 both of which had rolling terrain showed the highest heart rates.  However, there were other crits I attempted which did not feature high heart rates (for example Stage 18). Although the normalized power for those stages were also high, this does not explain the higher heart rates. Perhaps cadence is another factor that might offer a clue, meaning the stages with higher cadence could feature high heart rate. There may also be a hidden con-founder somewhere that is outside of this data (diet, sleep, fatigue, other activities in life...). 

3. Power : In terms of normalized power, the range was from 117W-193W over 21 days of racing. Interestingly, in the early stages, I was just getting used to riding at high intensities on a trainer and not wholly happy with the cooling air flow available to me. So the early stages featured low powers at high heart rates. As the stages evolved, I got fitter in terms of being able to deliver higher power to the pedals at similar heart rates. I also got myself a bigger industrial size fan which could push out more air volume! There was a plateauing phenomena in powers as racing progressed which I attribute to day-to-day fatigue and the inherent power curve limitations of the non-smart trainer. 

4. Aggregate stress : Over 21 days of riding, the aggregate TRIMP based stress was 3008 for a daily stress of 143 AU/day. The aggregate Bikestress (a correlate for TSS) was around 2124, giving a daily figure of 101 AU/day. These were all calculated in Golden Cheetah. Total kilojoules burned was 12036, resulting in an average of 573 KJ/day. On a per day basis, these numbers are higher than the same data from Poor Man's Giro d'Italia. 

5. Distance specific intensity : In terms of TSS/km and Trimp/km, two metrics that maybe indicative of ride intensity as a function of unit distance, the highest values were incurred in stages that featured either a mountain climb time trial, a mountain race or a high intensity crit race. For example, of all the stages, the ones I rode on Stage 3 (L'Etape du Tour Stage 3) and Stage 7 (Alpe du Zwift TT) posted the highest values of intensity per distance. This again agrees with my findings previously from Poor Man's Giro d'Italia and the research data I posted there from the Sanders et.al investigation of Grand Tour racing. With distance and per day stress metrics stated as above, one area of inquiry is whether there are differences in the numbers between indoor and outdoor racing. With constraints of air flow and cooling indoors, one might expect to see higher race intensities indoors. Comparing the Zwift racing data with last year's Poor Man's Giro, the distance specific intensity metric Trimp/km is definitely higher this year on Zwift. However, this is not exactly an apples-to-apples comparison because I did not do a true "stage racing simulation" last year. However, the argument that indoor intensities should be higher is a rational one and something to discuss and further explore. 

6. RPE : Across 21 days of racing, RPE varied from a low of 6 to 10! The hardest I felt was during Stage 2 (L'Etape du Tour) which featured a mountain ascent of 1538m. Because this was one of the earlier stages, I was in no shape to climb continuously for 3 hours with poor air flow. In the final 20 minutes, I did hop off the bike once to take a break, thinking I was going to have a heart attack. Part of the challenge that day in my pain cave was lack of air flow to cool myself for that long! The standard deviation in RPE across the stages was quite low, however, indicating that the intensity on all days were more or less quite similar to each other. 

7. Cadence : My average cadence across all stages was 83 and the highest cadence was during Stage 19 which was a rolling hills ITT of 28 kilometers in length, where I staved off fatigue by riding at 90+ cadence. I reckon the stages with high cadence were excellent stimulus to the VO2max region of training intensities. One of the things I'm pleased with as I attempted this challenge is that I got quite experienced with being able to regulate my cadence to tune my perceived effort within different racing situations. It may have been that this factor also affected my heart rates over the course of each day's race. 

8. Nutrition : In general, being able to race everyday on Zwift means having to rely heavily on carbohydrates; success seems to depend on how well the stores of glycogen are topped up between races. My fuel system is one that is biased towards carbohydrates, which may also be partly explained by the fact that I'm a habitual carbohydrate consumer. There were some days were I didn't have the luxury to manage the diet well to feel fully topped up before the next race. On days where I felt I needed an extra "boost", I used the top ergogenic aid known to man, that's right - Coca Cola! Caffeine works. 

9. Race Competition : In general, I have only good things to say about Zwift as it is a tremendous motivational tool. During the 21 stages, I enjoyed many days sitting in the peloton and sharing the effort that got us all across the line with good timings. But I was in no way a match for those who could utilize some of the "gaming" aspects of virtual racing.

I think Zwift has to figure out some way to weed out sandbaggers. Although the final listings on Zwift Power website excludes those cheating below their actual categories, the race dynamics are affected by the presence of these individuals. For example, it is often the first 2-3 minutes of an e-race where your placement is made or broken due to massive power surges to find position. The presence of more able riders who are cheating below their category could compel others to ride just as hard in order to get on their wheel , as a result many gaps are formed disadvantaging the lower order riders who have "missed the draft". This point maybe moot. 

Figure 3 : The author in a "break" of select group of riders from the Namibian Race League
during Stage 17. 


CONCLUSION

The results discussed in the section above generally agrees with the data found from Grand Tours that the mountain stages are where the action really is in-terms of stress and intensity. Although the race intensities were high and the monotony day to day was also high. Zwift provided a great way to beat that monotony with the ability to select from numerous races spread across different maps with different competitors. For example, I found South Africans and Japanese race subtly different when compared to Brits! Maybe that is an imaginative observation, but it still is an observation. 

Overall, while I found I was making improvements in the duration specific power outputs as the races progressed, I found myself hitting a plateau due to a combination of fatigue and power curve limitations on the trainer. In other words, there were diminishing returns after a point. 

From the Poor Man's Tour de France racing challenge, I was quickly able to learn which e-races suit me and which races wouldn't. Therefore, the choice of many rolling hilly races was intentional. I also included mountain stages. Flat, all-out races were few. 

If I redid the Poor Man's Tour de France again, I'd figure out a way to balance out the percentage of race distance spread between mountains, flats and rollers. But I can't say if the actual Tour de France traditionally or even this year has actually been balanced either! Often we hear that the Tour stages are deliberately designed to suit some of the top French stars. That doesn't seem to be any different this year. 

In conclusion, with the constraints that were upon my time, I think this was sufficient racing stimulus. Due to the plateauing effect of power and the accumulating fatigue as the stages progressed, I had to draw the line somewhere to minimize the losses. 

With a few days left for the actual Tour de France, I will be able to smugly soak in the racing footage and maybe even pretend to co-relate to it with my own experience, ha!