Need help understanding and implementing determinism for my fighting game

I don’t under stand how fighting games are able to be made and be deterministic at the same time. For example how am I supposed to keep track of the players position without using float!? Let alone accelerate them at a fixed reasonable speed? I don’t know how I would do this and I really need help. I used gemini to help me write a vector3 fixed point class but I’m still not confident that this is good enough to use it.

using System;
using Godot;

namespace ProjectKazen.Framework.Util;
public class FixedPoint
{
protected const int Shift = 16;
protected const long One = 1L << Shift;
public static float LongToFloat(long Value) => (float)Value / One;
public static long FloatToLong(float Value) => (long)MathF.Floor(Value * One);

public class Vector3 : FixedPoint
{
    public readonly long RawX;
    public readonly long RawY;
    public readonly long RawZ;
    public Godot.Vector3 ToFloatData => new(LongToFloat(RawX),LongToFloat(RawY),LongToFloat(RawZ));
    //Convert the raw into deterministic values by shifting left because ints have a less bytes than longs
    public int X => (int)(RawX >> Shift);
    public int Y => (int)(RawY >> Shift);
    public int Z => (int)(RawZ >> Shift);
    public Vector3I FixedData => new(X, Y, Z);
    public Vector3(int X, int Y, int Z)
    {
        //Shift XYZ by 16 to the left and convert to long because the number the ints can represent need to be bigger
        RawX = (long)X << Shift;
        RawY = (long)Y << Shift;
        RawZ = (long)Z << Shift;
    }
    public Vector3(long X, long Y, long Z)
    {
        RawX = X;
        RawY = Y;
        RawZ = Z;
    }
    public static implicit operator Vector3I(Vector3 Self) => Self.FixedData;
    // Math operators cause the IDE can't do it for us 
    public static Vector3I operator +(Vector3 Self, Vector3I Other) => new(Self.X + Other.X, Self.Y + Other.Y, Self.Z + Other.Z);
    public static Vector3I operator -(Vector3 Self, Vector3I Other) => new(Self.X - Other.X, Self.Y - Other.Y, Self.Z - Other.Z);

    public static Vector3 operator +(Vector3 Self, Vector3 Other) => new(Self.RawX + Other.RawX, Self.RawY + Other.RawY, Self.RawZ + Other.RawZ);
    public static Vector3 operator -(Vector3 Self, Vector3 Other) => new(Self.RawX - Other.RawX, Self.RawY - Other.RawY, Self.RawZ - Other.RawZ);

    public static Vector3 operator /(Vector3 Self, float Divisor)
    {
        long ConvertedDivisor = FloatToLong(Divisor); //Convert Divisor to long so it is deterministic 
        if (ConvertedDivisor == 0) return Self;
        //Shift Left before the division for all of the raw values
        return new((Self.RawX << Shift) / ConvertedDivisor, (Self.RawY << Shift) / ConvertedDivisor, (Self.RawZ << Shift) / ConvertedDivisor); 
    }

    public static Vector3 operator *(Vector3 Self, float Multiplier)
    {
        long ConvertedMultiplier = FloatToLong(Multiplier);
        //Multiply before the multiplication for all raw values
        return new((Self.RawX * ConvertedMultiplier) >> Shift, (Self.RawY * ConvertedMultiplier) >> Shift, (Self.RawZ * ConvertedMultiplier) >> Shift);
    }
    public long MagnitudeSquared()
    {
        return (RawX * RawX) + (RawY * RawY) + (RawZ * RawZ);
    }
    public Vector3 MoveToward(Vector3 to, float delta)
    {
        Vector3 vector = this;
        Vector3 vector2 = to - vector;
        float num = vector2.MagnitudeSquared();
        if (num <= delta || num < 1E-06f)
        {
            return to;
        }
        return vector + vector2 / num * FloatToLong(delta);
    }
}
}

I tried commenting it my self so I could understand it but I gave up trying to understand why I shift during specific math operations. If any one could help me or point me to a source I would be very greatfull! From what I can understand is that longs have a wider range of numbers because it’s 64 bits and we use that instead of ints because it’s 32 and I think the idea behind this is to use longs to and bit shifting to make it left it makes the number bigger. But shifting it right makes it smaller. Also I’m am supposed to shifting during specific math operations but I also don’t understand why we shift before or after certian parts of it. Specificly for multiplication and division. Other than that I don’t know what I’m doing

“For example how am I supposed to keep track of the players position without using float!?” Not sure what this means.

It seems you are trying to implement Godot math methods in C#.
https://math.stackexchange.com/questions/tagged/geometry?tab=Newest

A suggestion if you are inclined is check the godot source code implementation of things like MoveToward.

“For example how am I supposed to keep track of the players position without using float!?” But I don’t understand why we need to use float. I mean, can’t we use a big integer type instead of float and then let the game treat the huge number as a small number?

Floats are deterministic, maybe you are incorrectly conflating delta-time with all floating point values?

Why do you need your game to be deterministic? Are you making a replay/demo system? Are you implementing roll-back netcode? Are you trying to randomly generate something?

There are a lot of questions that needs to be clarified here.
Do you need 100% determinism because you are making a multiplayer fighting game?
Why does a normal float cause issues for you?
By determinism, do you also mean the frame perfect animations and invincibility windows and such?

If you could clarify your problems, you’ll find your answers more easily.

For writing better asking for help posts, I recommend giving a read to this:
XY Problem

Floats are usually always deterministic if they run on the same machine (hardware).

If the floating operations are not on the same machine, depending on a lot of factors like CPU, architecture etc. you may (and probably most definetly) end up with different precisions and accuaracy (which is not deterministic anymore across machines).

Maybe the Question Asker creates a multiplayer fighting game, which is peer to peer and is not running on the same machine. But this is only interpretation, I dont really know.

I’d be surprised to see a Godot target platform that doesn’t use the standard IEEE-754 floating point. x86, ARM, and RISC-V all do. Godot is going to compile with Vector types using single-precision floats and float using double-precision. You could force some compiler options to alter this and make differing targets, but that seems oddly intentional for trying to achieve determinism.

Thanks for the information regarding the Vector Types single precision and float using double precision, I didnt know that.
Yeah you are right with your last point.

I still want to add, even though if “everyone” is using IEEE-754, it still doesnt mean that cross machine determinism is guranteed (my previous claim was really vague).

If the Question Asker wants to create a fighting game which is only “simulated” on one host (even if its a multiplayer game), I think determinism regarding floats shouldnt worry him (as you already pointed out, why does he even need it).