Validate transform matrix format. Throws when the matrix appears to be
stored row-major (NumPy) rather than column-major (THREE.js / OpenGL),
which is almost always a producer bug — column-major translation lives
at indices [12,13,14], row-major at [3,7,11].
Edge case (both translation bands non-zero): a producer bug that
encodes a row-major matrix WITH a non-zero col-major slot (e.g.
row-major + shear, or row-major + scale touching index 12) used to
pass silently as "probably column-major". The current contract is to
throw a descriptive "ambiguous" error so the producer fixes the
encoding rather than letting the viewer place geometry in the wrong
location. Genuine column-major matrices have a [0, 0, 0, 1] last
row, so we use indices [3, 7, 11] as the disambiguator.
Validate transform matrix format. Throws when the matrix appears to be stored row-major (NumPy) rather than column-major (THREE.js / OpenGL), which is almost always a producer bug — column-major translation lives at indices [12,13,14], row-major at [3,7,11].
Edge case (both translation bands non-zero): a producer bug that encodes a row-major matrix WITH a non-zero col-major slot (e.g. row-major + shear, or row-major + scale touching index 12) used to pass silently as "probably column-major". The current contract is to throw a descriptive "ambiguous" error so the producer fixes the encoding rather than letting the viewer place geometry in the wrong location. Genuine column-major matrices have a
[0, 0, 0, 1]last row, so we use indices [3, 7, 11] as the disambiguator.