Maths¶
Verified against: SLUS_212.15 (NTSC-U, SHA1 e9cb2cc49aa046b9e494313dce2f5038ed17b2f4).
Purpose¶
The small maths unit every other directory calls: the game's random numbers, a fast arctangent, quaternions, transforms and matrices, distances, ground-plane tests, ray-triangle tests and bounding boxes. Pages elsewhere cite these by address; this page says what each one computes, so a reimplementation can use ordinary maths code with the same conventions and edge cases. What a reimplementation must keep is listed under What a reimplementation must keep.
Original structure¶
The unit has no path string: it sits at 0x00335320-0x00338420, between the Lua library and Memory/, with two
static-init stubs (0x00335670, 0x003383e0), so it is probably two files, the random numbers and the rest
(inferred, Source map). Names are ours; every address below has the same name and a plate comment
in the Ghidra project. Unless a row says otherwise its evidence is the code at that address.
Random numbers¶
The generator is a fixed table of 1,024 32-bit numbers at 0x005117e0. A stream is a 32-bit index: each draw
adds 1, masks it to 10 bits and reads that entry (Random_NextRaw), so a fresh stream's first draw reads entry 1.
There are ten indices, 0x006eb870-0x006eb8b8 (8 bytes apart), all zeroed at start-up and never seeded; every
function below takes the stream in a0 and passes it through. Most callers pass 0x006eb880 (the Lua random
binding, items, brains, humans) or 0x006eb8b8 (particles, strikes, glass), directly or from a saved register;
confirmed (code) for those two, the use of the other eight not traced. How random(a, b) depends on the draw count:
World flags.
Two integer forms differ at the top: Random_Int(n) returns 0 to n inclusive (raw mod (n + 1)), while
Random_IntBelow(n) returns 0 to n − 1.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x00335320 |
Math_NextPowerOfTwo |
The smallest power of two not below n (n itself when it is one). | confirmed (code) |
0x00335390 |
Random_NextRaw |
Next 32-bit number of a random stream: the stream's index (a0) is advanced by 1 and masked to 10 bits, then entry index of the fixed 1,024-entry table at 0x005117e0 is returned (never seeded). |
confirmed (code) |
0x003353b8 |
Random_Int |
Random integer 0 to n inclusive from stream a0: raw mod (n + 1), unsigned; trap 7 when n + 1 is 0. | confirmed (code) |
0x003353f0 |
Random_IntRange |
Random integer low to high inclusive: low + Random_Int(high - low). The Lua random binding's core. | confirmed (code) |
0x00335420 |
Random_FloatRange2 |
Random float in [lo, hi): Random_Unit × (hi - lo) + lo; the same as Random_FloatRange, compiled apart. | confirmed (code) |
0x00335460 |
Random_FloatRange |
Random float in [lo, hi): Random_Unit × (hi - lo) + lo. | confirmed (code) |
0x00335498 |
Random_IntBelow |
Random integer 0 to n - 1 (raw mod n, signed); 0 when n is 0. | confirmed (code) |
0x003354e0 |
Random_Unit |
Random float in [0, 1): the raw number (unsigned) × 2⁻³². | confirmed (code) |
0x00335540 |
Random_Signed |
Random float in [-1, 1): 2 × Random_Unit - 1. | confirmed (code) |
0x00335570 |
Random_ScaledVector |
Random vector (Random_Unit × sx, Random_Unit × sy, Random_Unit × sz, 1), each axis drawn in turn. | confirmed (code) |
0x00335608 |
Random_StaticInit |
Static initialiser: zeroes the ten random-stream indices 0x006eb870-0x006eb8b8 (8 bytes apart). |
confirmed (code) |
0x00335670 |
Random_StaticInitStub |
Static-init entry: Random_StaticInit(1, 0xffff). | confirmed (code) |
Arctangent¶
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x003356a0 |
Math_AtanApprox |
atan(x) by a rational approximation: x folded to [0, 1] (1/x, then π/2 - result), above tan(π/12) shifted by π/6 with tan(π/6) (0x006eb908, 0x006eb904), then x(0.99999905 + 0.25797766 x²) / (1 + 0.5912045 x²). |
confirmed (code) |
0x003357a8 |
Math_Atan2 |
atan2(y, x) in (−π, π] from the same approximation as Math_AtanApprox; π/2 when x is within 5e-7 of 0 or either argument is the all-ones NaN at 0x005116b0. |
confirmed (code) |
Angles, quaternions, transforms and matrices¶
A transform is {position, quaternion at +0x10} (16 bytes each, w = 1 for points); quaternions are (x, y, z, w);
matrices are 4 rows of 16 bytes used with row vectors, the translation in the fourth row. Headings are radians
clockwise from +y (the Camera convention).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x003359b0 |
Transform_Compose |
Composes transforms {pos, quat at +0x10}: out.quat = a.quat × b.quat, out.pos = a.pos + b.pos rotated by a.quat (b expressed in a's frame). |
confirmed (code) |
0x00335a78 |
Transform_ApplyPoint |
A point through a transform: p rotated by the transform's quaternion (+0x10) plus its position; w = 1. |
confirmed (code) |
0x00335b08 |
Quat_IntegrateAngular |
Integrates an angular velocity into a quaternion in place: q += 0.5 × dt × (w (x) q), then normalised (identity when it collapses to 0). | confirmed (code) |
0x00335c08 |
Quat_IntegrateAngularTo |
As Quat_IntegrateAngular but writes the result to a separate output: out = normalise(q + 0.5 × dt × (w (x) q)). | confirmed (code) |
0x00335d08 |
Angle_Wrap2Pi |
Wraps an angle in radians into [0, 2π) by repeated steps of 2π. | confirmed (code) |
0x00335d98 |
Vec_Heading |
Heading of a vector in [0, 2π): atan2(x, y), so clockwise from +y. | confirmed (code) |
0x00335e00 |
Vec_HeadingFromTo |
Heading in [0, 2π) of b - a (from point a toward point b), by Vec_Heading. | confirmed (code) |
0x00335e40 |
Vec_FromHeading |
Unit direction for a heading in radians: (sin h, cos h, 0, 1), clockwise from +y. | confirmed (code) |
0x00335ea0 |
Quat_FromAxisAngle |
Quaternion from an axis and an angle in radians: (axis × sin(a/2), cos(a/2)). | confirmed (code) |
0x00335f48 |
Quat_Heading |
Heading of a quaternion in [0, 2π): Vec_Heading of its rotated y axis (Quat_AxisY). | confirmed (code) |
0x00335f70 |
Quat_AngleFromX |
Angle of a quaternion's rotated y axis from +x, counter-clockwise: atan2(y.y, y.x) in (−π, π]. | confirmed (code) |
0x00335fa0 |
Mat_Invert |
General 4x4 inverse of a transform matrix (rows at 0, 0x10, 0x20, 0x30): transposed into a scratch array, Gauss-Jordan elimination with partial pivoting against the identity (0x005117a0), transposed back. |
confirmed (code) |
0x00336308 |
Quat_AxisX |
A quaternion's rotated × axis: (1 - 2(y² + z²), 2(xy + zw), 2(xz - yw), 1). | confirmed (code) |
0x003363b0 |
Quat_AxisY |
A quaternion's rotated y axis (its forward): (2(xy - zw), 1 - 2(x² + z²), 2(yz + xw), 1). | confirmed (code) |
0x00336458 |
Quat_AxisZ |
A quaternion's rotated z axis (its up): (2(xz + yw), 2(yz - xw), 1 - 2(x² + y²), 1). | confirmed (code) |
0x00336500 |
Quat_ToMatrix |
A quaternion to a 4x4 rotation matrix (rows are the rotated axes), translation zero and w = 1. | confirmed (code) |
0x003365c8 |
Mat_ToQuat |
A rotation matrix to a quaternion: from the trace when it is positive, else from the largest diagonal element (the usual branch order 1, 2, 0). | confirmed (code) |
0x00336808 |
Transform_ToMatrix |
A transform {pos, quat at +0x10} to a 4x4 matrix: Quat_ToMatrix rows, the position as the fourth row. |
confirmed (code) |
0x003368d8 |
Mat_SwapYZ |
Re-expresses a 4x4 matrix with y and z exchanged (y negated first, pexcw), rows 1 and 2 swapped, w words cleared and the last row's w = 1: the change between the game's z-up frame and a y-up one (inferred). Used by the render paths (glass, fog, embers, tag HUD). | inferred |
0x00336940 |
Mat_MulSwapYZ |
Multiplies two 4x4 matrices (row vectors: out = b × a, b applied first) and converts the product as Mat_SwapYZ. The skeleton update's bone matrices. | confirmed (code) |
0x00336a00 |
Quat_Slerp |
Spherical interpolation of two quaternions by t clamped to [0, 1]; takes the shorter arc (negates when the dot is negative) and falls back to a linear mix within 1e-6 of parallel. | confirmed (code) |
0x00336bb8 |
Vec_Lerp |
Linear mix of two vectors: out.xyz = a × (1 - t) + b × t; w kept from a. | confirmed (code) |
0x00336bf8 |
Quat_Nlerp |
Normalised linear interpolation of two quaternions: a when their dot is exactly 1, else normalise(a(1 - t) + b t) with b negated when the dot is negative. | confirmed (code) |
Distances, lines and look-at¶
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x00336c98 |
Vec_DistSq2D |
Squared distance of two points in the ground plane (x, y). | confirmed (code) |
0x00336ce0 |
Vec_DistSq |
Squared distance of two points (x, y, z). | confirmed (code) |
0x00336d28 |
Segment_PointDistSq |
Squared distance from a point to a segment (via Segment_ClosestPoint). | confirmed (code) |
0x00336d68 |
Vec_Dist2D |
Distance of two points in the ground plane: sqrt(Vec_DistSq2D). | confirmed (code) |
0x00336d88 |
Vec_Dist |
Distance of two points: sqrt(Vec_DistSq). | confirmed (code) |
0x00336da8 |
Vec_Dir2D |
Unit direction in the ground plane from a to b: (b - a).xy normalised, z = 0, w = 1. | confirmed (code) |
0x00336e08 |
Line_ClosestPoint |
Closest point to p on the infinite line through a and b: a + d × dot(p - a, d), d the unit direction. | confirmed (code) |
0x00336ee0 |
Segment_ClosestPoint |
Closest point to p on the segment a-b: a when the projection is at or before a, b when at or past b, else the projection. | confirmed (code) |
0x00337028 |
Mat_LookAt |
Orientation matrix looking along (target - position): returns 0 when they are within 0.005 m; the up vector is +z (0x00511700), or (1, 1, 1) normalised when the direction is within 0.98 of vertical; then Mat_LookAtUp. Every camera class's placement. |
confirmed (code) |
0x00337168 |
Mat_LookAtUp |
Builds an orthonormal basis from a direction and an up vector: row 1 (+0x10) the unit direction, row 0 = direction × up normalised, row 2 = row 0 × direction normalised, w words 0; false when the direction is shorter than 0.005 m. |
confirmed (code) |
Ground-plane tests, angle steps and small helpers¶
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x003372a0 |
Math_Orient2D |
Which side of a-b point c lies on in the ground plane: 1 (left, counter-clockwise), -1 (right) or 0 (on the line), from the 2D cross product. | confirmed (code) |
0x00337308 |
Segment_Intersect2D |
Whether two segments p1-p2 and p3-p4 cross in the ground plane: p1 and p2 on different sides of p3-p4 and p3 and p4 on different sides of p1-p2 (Math_Orient2D). | confirmed (code) |
0x003373b8 |
Angle_StepToward |
Turns an angle in [0, 2π) toward a target by at most step, the shorter way; within slow (when positive) the step is scaled by the remaining distance / slow; returns the target when the step reaches it. Human_UpdateHeadLook. | confirmed (code) |
0x003374b8 |
Angle_Diff |
Unsigned shortest difference of two angles in [0, 2π), 0 to π. | confirmed (code) |
0x00337568 |
Math_MapRange |
Linear map of × from [inMin, inMax] onto [outMin, outMax]. | confirmed (code) |
0x003375a0 |
Math_RoundUpToMultiple |
Rounds n up to a multiple of m: (n - 1 + m) - (n - 1) mod m; n when m is 0. | confirmed (code) |
0x003375d0 |
Math_CircleRadius2D |
Radius of the circle through a and b (ground plane) whose centre lies from a along the direction c: length(b - a) / (2 abs(dot(unit(b - a), c))); 0 when a and b coincide or the dot is 0. | confirmed (code) |
0x003376c0 |
Vec_PointOnCircle |
Point on a circle in the ground plane: centre + r × (cos a, sin a, 0) with a in°rees (counter-clockwise from +x); w = 1. | confirmed (code) |
0x00337788 |
Vec_PointOnCircleCopy |
Vec_PointOnCircle with the centre copied from a pointer first. | confirmed (code) |
0x003377c0 |
Quat_FromDirection |
Quaternion facing along a direction: an up of +z (0x00511740; +x, 0x00511720, when the direction is within 0.99 of vertical), two cross products for the basis, then Mat_ToQuat. |
confirmed (code) |
Roots, rays and segments¶
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x003378b0 |
Math_QuadraticLargerRoot |
Larger real root of a x² + b × + c = 0; nothing written when the discriminant is negative. | confirmed (code) |
0x00337920 |
RayTriangle_OneSided |
Ray-triangle test (Moller-Trumbore), front faces only (determinant at least 1e-6): the hit distance along the ray, or failure. | confirmed (code) |
0x00337a60 |
RayTriangle_TwoSided |
Ray-triangle test (Moller-Trumbore) for either face (determinant at least 1e-6 in size): the hit distance along the ray, or failure. | confirmed (code) |
0x00337bd8 |
Segment_SegmentDistSq |
Squared distance between two segments: the closest parameters s and t on each, clamped to [0, 1], with the near-parallel case (denominator below 0.005) taking s = 0. TrainRecord_IsPathClear. | confirmed (code) |
Boxes, colours and start-up¶
A box is {min, max at +0x10}.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x00337ed8 |
Box_SetEmpty |
Box {min, max at +0x10} made empty: min = FLT_MAX (0x00576a7c), max = -FLT_MAX (0x00576a80), so any point extends it. |
confirmed (code) |
0x00337f08 |
Box_FromPoint |
Box with min = max = a point. | confirmed (code) |
0x00337f28 |
Box_Union |
Box grown to contain another box (component-wise min of the mins, max of the maxes). | confirmed (code) |
0x00337f78 |
Box_AddPoint |
Box grown to contain a point. | confirmed (code) |
0x00337fc8 |
Box_Centre |
A box's centre: min + (max - min) × 0.5. | confirmed (code) |
0x00338048 |
Box_ContainsPoint |
Whether a point is inside a box, bounds included. | confirmed (code) |
0x003380d8 |
Box_Overlaps |
Whether two boxes overlap on all three axes, touching included. | confirmed (code) |
0x00338178 |
Box_Corners |
A box's eight corners (16 bytes apart) from its min and max. Camera_FrustumTestBox and the occluders. | confirmed (code) |
0x00338240 |
Colour_LerpRatio |
Blends two RGBA byte colours by t = a / b: the bytes unpacked to floats, c1 × (1 - t) + c2 × t, repacked; 1 - t and t written out; c1 when the colours are equal. cars.md, the HUD. | confirmed (code) |
0x00338300 |
Maths_StaticInit |
Static initialiser: copies the identity quaternion (0x005116c0), 0x00511780 and the identity matrix rows (0x005117a0-0x005117d0) into 0x006eb8c0-0x006eb94c and stores tan(π/6) (0x006eb904) and tan(π/12) (0x006eb908) for Math_AtanApprox. |
confirmed (code) |
0x003383e0 |
Maths_StaticInitStub |
Static-init entry: Maths_StaticInit(1, 0xffff). | confirmed (code) |
What a reimplementation must keep¶
- The random table and its streams where a page depends on a draw order: the table comes from the player's
executable at run time (
GameRandom, World flags), andRandom_Int(n)includes n. Elsewhere any generator with the same ranges will do. - Headings clockwise from +y (
Vec_Heading,Vec_FromHeading,Quat_Heading): angles stored in scripts and flags use it;Vec_PointOnCircleandQuat_AngleFromXuse the mathematical convention instead. - Transforms as
{position, quaternion}composed asTransform_Composedoes (the child in the parent's frame). - The degenerate cases callers rely on:
Mat_LookAtfails within 0.005 m and swaps its up vector near vertical;Math_Atan2returns π/2 for x near 0;Quat_Slerpclamps t to [0, 1].
Open questions¶
- Which callers use the other eight random streams (
0x006eb870-0x006eb8b0, except0x006eb880). - Whether
Mat_SwapYZconverts to RenderWare's frame or to the vector unit's (its callers are all render paths).