Decane / SRP

Sky Reclamation Project for S.T.A.L.K.E.R.: Clear Sky
http://www.moddb.com/mods/srp
119 stars 20 forks source link

`game.time()` wraps around after ~30 game days #153

Open Decane opened 1 year ago

Decane commented 1 year ago

It turns out game.time() wraps around to 0 after ~30¼ game days ((2³² - 1) - 1,679,936,192 game milliseconds). One option would be to convert the C++ struct returned by game.get_game_time() into seconds and use that in place of game.time() wherever sub-second precision is not required. The Lua built-in os.time function could be used for the conversion, albeit at the cost of sub-second rounding errors. Here is an example of how to implement that: https://github.com/Decane/SRP/commit/9919135f6916c04dae5fabc762756f2cacc338d4. If millisecond precision were required, get_game_time_in_seconds() in the aforementioned commit could be modified to return a fractional os.time(time_table) + ms * 0.001, however saving it without losing precision might be tricky since the engine doesn't expose a double-precision floating point save method.

Saving a large u32 — like that returned by get_game_time_in_seconds() in the commit above, assuming not modified for fractional precision — as a float32 is a bad idea; we begin losing precision with odd numbers above 16,777,216 (2²⁴) and even numbers above 33,554,432 (2²⁵). That means we can only save up to ~194.18 days of seconds into a float32 before we begin seeing ±1 second rounding errors. Past ~388.36 days, these grow to ±2 seconds. If counting were to begin from the epoch (as in os.time), we'd already be way over the precision limit when the game starts at 06:10 2011/09/10, and our rounding errors would be immense (±64 seconds). Even if counting began from 2011/01/01 instead, we'd be up to 21,795,000 seconds when the game starts. We could hard-code the counting to begin at the vanilla game start time, but that could break mods that use a different start time. As there seems to be no way to access the game start time directly from Lua, my solution against game.time() precision loss in https://github.com/Decane/SRP/commit/f4b5ce8451b30f1713eba0842a91f8e0cee6abc4 was to detect fractional numbers in xr_logic.pstor_save_all and save those as float32, else save as s32 or u32 yielding 2,147,483,647 (2³¹ - 1) or 4,294,967,295 (2³² - 1) seconds of wiggle room, respectively.

To save a fractional get_game_time_in_seconds() without precision loss would likely require saving the fractional part separately, perhaps as a u16.

Decane commented 1 year ago

Another quirk of vanilla X-Ray seems to be that the precision of integers >= 2³¹ saved as u32 is limited to the nearest multiple of 256. So, any integers >= 2,147,483,648 will be rounded to the nearest multiple of 256 when saved using e.g. xr_logic.pstor_save_all. Thus, if the number saved is in milliseconds, then the number loaded would have a rounding error of up to 128 milliseconds. No big deal if we only need to-the-nearest-second precision, but suppose we're saving a number denominated in seconds. We'd have a rounding error of over 2 minutes in the worst-case scenario!