Fixed-Point Math¶
This is the most important page if you're writing Lua for SplashEdit. The PS1 has no floating-point unit, so all math uses fixed-point integers.
The Rules¶
- Never write decimal literals.
0.5,1.5,3.14- none of these work. - Plain integer division does NOT give you fractions.
1/2equals0, not0.5. This is integer division. - Use
FixedPoint.new()for ALL fractional values. This is the only way to create non-integer numbers. - All API functions accept and return fixed-point numbers. Positions, rotations, distances - everything.
How It Works¶
Numbers in SplashEdit Lua are 20.12 fixed-point integers:
- 20 bits for the integer part
- 12 bits for the fractional part
- Scale factor: 4096 (4096 = 1.0 internally)
- Range: approximately -524288 to +524287
You don't need to think about the internal representation. Just follow the rules above.
Creating Fractional Values¶
The only way to get a fractional value is through FixedPoint.new():
-- Create a FixedPoint value and divide it
local one = FixedPoint.new(1)
local half = one / 2 -- 0.5
local quarter = one / 4 -- 0.25
local threeQuarters = one * 3 / 4 -- 0.75
local step = one / 64 -- ~0.015625
local small = one / 16 -- 0.0625
Common Mistake
-- WRONG: These all equal 0!
local half = 1/2 -- Integer division: 0
local frac = 5/4 -- Integer division: 1
local tiny = 1/64 -- Integer division: 0
local bad = 0.5 -- Float literal: doesn't work
-- CORRECT: Use FixedPoint.new()
local one = FixedPoint.new(1)
local half = one / 2 -- Proper fixed-point 0.5
local frac = one * 5 / 4 -- Proper fixed-point 1.25
local tiny = one / 64 -- Proper fixed-point ~0.016
Whole Numbers Are Fine¶
Plain integers work as-is for whole numbers:
You only need FixedPoint.new() when you need a value with a fractional part.
Rotation Values¶
Rotations use pi-units: 1024 internal units = pi radians = 180 degrees.
| Angle | Lua Value | How to Write |
|---|---|---|
| 0 | 0 |
0 |
| 45 degrees | 0.25 pi | FixedPoint.new(1) / 4 |
| 90 degrees | 0.5 pi | FixedPoint.new(1) / 2 |
| 180 degrees | 1 pi | 1 |
| 270 degrees | 1.5 pi | FixedPoint.new(3) / 2 |
| 360 degrees | 2 pi | 2 |
local one = FixedPoint.new(1)
-- Rotate 90 degrees
Entity.SetRotationY(self, one / 2)
-- Add 180 degrees to current rotation (1 is a whole number, fine as-is)
local rot = Entity.GetRotationY(self)
Entity.SetRotationY(self, rot + 1)
-- Small rotation
Entity.SetRotationY(self, rot + one / 64)
Math Functions¶
Math functions work with fixed-point numbers. Remember to use FixedPoint.new() for fractional arguments:
local one = FixedPoint.new(1)
-- PSXMath utilities
local clamped = PSXMath.Clamp(value, 0, 100)
local lerped = PSXMath.Lerp(0, 10, one / 2) -- Interpolate halfway: result is 5
local sign = PSXMath.Sign(-42) -- Result: -1
local abs = PSXMath.Abs(-7) -- Result: 7
Vec3 Operations¶
Vectors are tables with x, y, z fields. Fractional components require FixedPoint:
local one = FixedPoint.new(1)
local pos = Vec3.new(10, 0, 5)
local dir = Vec3.new(one / 2, 0, one / 2)
local sum = Vec3.add(pos, dir)
local dist = Vec3.distance(pos, Vec3.new(0, 0, 0))
String Concatenation¶
FixedPoint values work with the .. string operator:
Common Gotchas¶
Integer division is silent
1/2 silently evaluates to 0. There's no error, no warning. Your code runs, but with wrong values. Always use FixedPoint.new() for fractions.
Integer overflow
Very large multiplications can overflow the 32-bit fixed-point range. Keep intermediate values reasonable.
Reuse your FixedPoint local
Create local one = FixedPoint.new(1) once at the top of your script or function, then derive all fractions from it. Don't call FixedPoint.new() inside hot loops.