Loading...
  OR  Zero-K Name:    Password:   
Title: Steel_Blue's Bugnazium
Host: USrankAdminSteel_Blue
Game version: Zero-K v1.8.11.3
Engine version: 104.0.1-1544-ge1f249f
Battle ID: 998336
Started: 5 years ago
Duration: 55 seconds
Players: 1
Bots: True
Mission: False
Rating: None
Watch Replay Now
Manual download

Team 1

USrankAdminSteel_Blue
Team 2

AI: Beginner (1) <DevCircuitAIBeginner64>
Spectators

Show winners



Preview
Filter:    Player:  
sort

5 years ago
Not my best moment, I should stop hitting myself.
+1 / -0
5 years ago
I've done a bit of digging.

The way the TraceRay implementation checks for friendly fire from a shot is a bit simplistic. It only checks that the initial point at the muzzle, and a sphere around an estimated impact point do not intersect any friendly collision volumes.

To calculate the estimated impact point, it first calculates the distance from the muzzle position to the collision volume's central position. I've marked this in the video below as a yellow vector. The collision volume's central position is marked as a red dot, and the collision volume itself is drawn as a purple wireframe.

This is then used to compute a theoretical impact point in 2D space. It takes the direction the weapon wants to fire, and moves at a length equal to the yellow vector's length from the muzzle in that direction. This is marked in the video below by a cyan vector when the test ultimately gives an all clear, and a magenta vector if the test ultimately blocks firing due to potential friendly fire.

This impact point is then corrected vertically up or down to reach where the projectile's height would be, given its parabolic arc of fire. This is marked in the video below by a small vertical green vector when the test gives an all clear, and a small vertical red vector when the test blocks firing.

Finally, this impact point is enlarged to a small sphere to simulate weapon inaccuracy/spread. This is not depicted below, as Ravager projectiles are quite accurate, so this changes very little.

If either the muzzle's position or this sphere are inside a friendly collision volume, the test will block firing. Otherwise, the test will permit firing.



So, what's happening here?


Immediately as the Ravager leaves the factory, the estimated impact point is fairly accurate, with the distance to the center of the collision volume and the distance to the real impact point being quite close.

At 0:05, the test returns a pass, despite the 2D point being over the collision volume. This is because the height at the estimated impact point now clears the collision volume, although in practice the projectile would clip the corner before it reaches this height.

The Ravager has no target for a while, so does not perform any further tests until 0:11.

At 0:11, the 2D estimated impact point is outside of the collision volume, though it's obvious that it will hit the collision volume before it reaches this point. From then until about 0:30, it does just that.

At 0:33, we see a repeat of the behaviour at 0:05, with the 2D estimated impact point being reasonable from this angle, and the test prohibiting fire until the adjusted estimated impact point gains enough height to clear the collision volume. As with what would have happened at 0:05, it clips the corner of the collision volume before could reaches this point.

At 1:00, we see a repeat of the behaviour from 0:11. We nearly get close enough for the test to begin blocking fire again at 1:06, but the Ravager stops just short of pulling the estimated impact point back into the collision volume. This continues until the end.


Now that we know what's happening, what now?


You can get false negatives with spherical or even cylindrical collision volumes, but this should be most noticeable with long box collision volumes, such as Siren, or most factories. You could probably expect a fleet of Sirens to cheerfully kill each other quite often, thanks to this effect.

The relevant code already remarks that this process can easily return false negatives, and that testing (x,f(x)) isn't sufficient. So Spring already knows that this is a problem. While this could be replaced with proper Ray Tracing, this is also currently a very fast test which gets called within very deeply nested loops. Roughly speaking: every collision volume for every unit on every quad on the way from every parabolic weapon from every unit at least every slow update.

Do we know how much of a problem this is in practice? Given an engine which performs more accurate hit tests, it might be helpful to see how it performs on large scale team games.

It might also be worth thinking about the other side of this test. While mathematically, there won't be any false positives for the with-inaccuracy sphere intersecting a friendly collision volume, a player is likely to often want a Stardust to fire in spite of a small chance of hitting friendly units.
+10 / -0
I think a better solution must be possible without much computational effort. If the tested collision volume has a circular base area, the current calculation is fine because errors are very unlikely. The current code can already be made a bit more efficient by checking muzzle position collision before the calculation of hitVec and hitPos and if true, skipping the rest of the calculation until the debug rendering. The calculation of "ret" can be made a bit more efficient, namely:
ret = (cv->GetPointSurfaceDistance(static_cast<const CUnit*>(obj), nullptr, tstPos) <= coneSize);
or
ret = (cv->GetPointSurfaceDistance(static_cast<const CUnit*>(obj), nullptr, hitPos) <= coneSize);
For tested collision volumes with rectangular base area, a better calculation can be done. I propose the following algorithm:

Define "ret" as whether the muzzle position is within the collision volume. If true, skip the rest of the calculation until the debug rendering. Otherwise do the current calculation until hitVec. Then check if the collision volume has a rectangular base area. If no, apply the current calculation but without checking the muzzle position.

Otherwise (rectangular base area), set
distToCenter = dist((tstPos + hitVec), collision volume center) - coneSize;
ret = (4*distToCenter * distToCenter <= (collison volume length * collison volume length + collision volume width * collision volume width));.
If ret is false, skip the rest of the calculation until the debug rendering. This excludes cases where we are for sure not hitting the target to safe computation time.

Otherwise, get the 4 border lines of the collision volume base area. Calculate the crossing points with the 2D projectile trajectory. Exclude the crossing points outside the border limits. Exclude the crossing points outside the trajectory limits. If the number of remaining crossing points is <= 0, the object is out of trajectory, so ret is false. Then, skip the rest of the calculation until the debug rendering.

Otherwise, calculate the 3D hit position for the first remaining crossing point and check if it's within the object. If yes, ret is true and go to debug rendering. Otherwise check through the remaining crossing points until one is found that is unequal the first crossing point. If no such point is found (the target is either within the base area or the trajectory goes exactly through a corner but not any other point of the base area), set ret as whether the target itself is within the collision volume and go to debug rendering. If one is found, calculate its 3D hit position and check whether it's within the object. If yes, ret is true and go to debug rendering. Otherwise check if the first crossing point 3D hitposition is above or below the object. Then check if the found next unequal crossing point 3D position is above or below the object. Set ret as the inequality of the two above/below states because then, the trajectory must go through the object even though the 2D entry and leave points don't. Don't check further crossing points because they can only be equal to the previous ones. Do debug rendering. Return ret.

[Spoiler]

[Spoiler]
+1 / -0
If a projectile flies above/below an object until the 2D center of the object but afterwards/before it goes deep/high enough, it can hit it if my above algorithm is not used or the base area is not rectangular. This can be fixed by checking the collision height at the entry and leaving 2D point as in my above algorithm for the rectangular case which would have to be generalized for other shapes. It should be easy to generalize it for circular base areas. Are there even any other base areas in the game?
+0 / -0


5 years ago
Bases can be ellipses.
+0 / -0
5 years ago
Oh ok, I was expecting that this was the next most likely shape. Is it even feasible to override parts of the engine in the game or should this better be done in the engine itself?
+0 / -0


5 years ago
Fixes for this should be in the engine, but those fixes could involve adding parameters that the game can tweak until things work well enough. I am wary of tossing aside the heuristics completely.
+0 / -0

8 hours ago
Adendum: solved a couple years back. I don't know if he knew this thread existed oh well.

https://github.com/beyond-all-reason/RecoilEngine/pull/891
+0 / -0

6 hours ago
Around the 40 seconds mark in the video by NZrankesainane you can see flickering. I think I've seen this in the game on my PC too.
+0 / -0