saul / demofile-net

Blazing fast cross-platform demo parser library for Counter-Strike 2, written in C#.
MIT License
76 stars 7 forks source link

Pawn position is outside of buy-zone during freeze time #37

Closed in0finite closed 2 months ago

in0finite commented 5 months ago

Research

Description

Sometimes, during freeze time pawn is placed outside of buy-zone.

To reproduce:

Go to: https://csunity-5172f.web.app/ Click Open -> Ok -> Select navi-javelins-vs-9-pandas-fearless-m1-mirage.dem -> Click Pause

Observe player "victoria" on the middle of screen. If you look around, you'll see his teammates are too far away from him.

Once the round starts, his position will be correct.

His position coordinates are again 1024. You can see that in Inspector (but converted to meters, it will be 32).

Code to reproduce

No response

Affected demos

navi-javelins-vs-9-pandas-fearless-m1-mirage.dem

saul commented 4 months ago

Does this issue reproduce on other CS2 online demo viewers? I'm interested to know if this is an issue with this parser specifically, or with the data that is in the demo/how it was recorded.

I'm also interested to know if you've seen this in any other demos, or just this one? It's possible that the issue has been fixed in the various demo recording/playback bugfixes that Valve have made over the past month or so.

saul commented 4 months ago

I can't seem to reproduce victoria's coordinates ever matching 1024. This yields nothing:

        demo.EntityEvents.CCSPlayerPawn.AddChangeCallback(
            pawn => pawn.Origin,
            (pawn, _, origin) =>
            {
                if (pawn.Controller?.PlayerName == "victoria" && (Math.Abs(origin.X) == 1024 || Math.Abs(origin.Y) == 1024 || Math.Abs(origin.Z) == 1024))
                {
                    Console.WriteLine($"{pawn.Controller} - {origin}");
                }
            });
in0finite commented 4 months ago
var demo = new DemoParser();

await demo.StartReadingAsync(File.OpenRead("test.dem"), default);

while (demo.CurrentDemoTick.Value < 1305)
{
    await demo.MoveNextAsync(default);

    var player = demo.Players.SingleOrDefault(p => p.PlayerName == "victoria");
    if (player != null)
    {
        Console.WriteLine($"[{demo.CurrentDemoTick.Value}] Victoria position: {player.Pawn?.Origin}");
    }
}

Console.WriteLine("\nFinished!");

Until tick 1240, his position is { X = 1024, Y = -1024, Z = -1024 }. This is when the round starts. Then, until tick 1301, his Z coordinate stays at -1024. And then, his position is correct.

This is all visible in demo viewer.

in0finite commented 4 months ago

Does this issue reproduce on other CS2 online demo viewers? I'm interested to know if this is an issue with this parser specifically, or with the data that is in the demo/how it was recorded.

I'm also interested to know if you've seen this in any other demos, or just this one? It's possible that the issue has been fixed in the various demo recording/playback bugfixes that Valve have made over the past month or so.

I didn't test it with other online viewers.

I've seen this in many demos. And not just in the 1st round. It seems random.

Also, there is still a bug with pawn's position jittering. It just happens more rarely than before. I think it's connected to this bug.

in0finite commented 4 months ago

I just opened #45 . I suspect it's the same issue.

In the same demo, you can see pawns outside of buy-zones : pause & demo_seek_tick 86464. kyxsan is outside.

Another example: same match, but on Nuke. Beginning of 2nd round (1-0 score). zont1x is outside.

in0finite commented 2 months ago

@saul I tested Golang library using the same demo. It gives different results :

tick 0 - 1240 (round starts at 1240): position is (1296.000000000000000000000000, -64.000000000000000000000000, -167.968750000000000000000000)

tick 1240 - 1300 : X and Y coordinates are changing, Z stays the same (-167.968750000000000000000000)

tick 1300 - ... : all coordinates are changing

======================================== In comparison, here are the results of this library :

tick 0 - 1240 (round starts at 1240): position is (X = 1024, Y = -1024, Z = -1024)

tick 1240 - 1300 : X and Y coordinates are changing, Z stays the same (-1024) , note that XY are identical to Golang library so they are correct

tick 1300 - ... : all coordinates are changing, identically to Golang library

===========================================

This makes me think that in some occasions, some entity properties are not updated at all (they are skipped). So, at tick 0, XYZ offset is not updated, that's why it remains the same until tick 1240. Also, at tick 1240, Z offset is not updated, so he remains the same until next time pawn changes position Z.

I can post the Golang code used for testing, if you need it.

in0finite commented 2 months ago

After digging through code of both libraries for entire day, I finally nailed it.

Both libraries read the position from exact same location in bit buffer, but the difference is that BitBuffer.ReadUBits(32) will return 0 if bits available is 32, because 1 << 32 gives 1, so the mask ((1 << numBits) - 1) becomes 0.

Phiiuu 😓

This means that everything that uses BitBuffer.ReadUBits(32), BitBuffer.ReadFloat(), BitBuffer.ReadAngle(), FieldDecode.DecodeFloatNoscale(), is potentially broken and is subject to jittering from 0 to real value.

Maybe use pre-calculated masks, it's a hot path afterall.

saul commented 2 months ago

Amazing work - many thanks for this! Great catch :)