BT410 5.3.65: THE MECH STANDS -- wire matrix convention proven from engine source

pose_chase.png: the MadCat assembled from its 19 parts, standing in the arena
at its live articulated position.  Feet, legs, torso, weapon pods, canopy
plate.  Not a collapsed stack at the origin, which is what every prior frame
actually showed.

The convention is no longer inferred -- it is read from the engine.
RootRenderable's ctor writes an entity pose into a DCS with
*(Matrix4x4*)dpl_GetDCSMatrix(myDCS) = localToWorld, so
Matrix4x4::operator=(const AffineMatrix&) IS the wire layout: row-major,
rotation in rows 0-2, zeros in column 3, translation in ROW 3 (entries
12/13/14).

The old 3/7/11 choice rested on two errors, both corrected in the code
comments: AffineMatrix does keep translation at 3/7/11, but because it is
COLUMN-major -- citing it as precedent for a row-major layout read the storage
and ignored the indexer (AFFNMTRX.HPP:99).  And 'the last row made the maths
blow up' came from the contaminated bisect; those crashes were the RIO fault.

Rotation now composes through the engine's own Matrix4x4::operator=(const
EulerAngles&) from the page's pitch/yaw/roll, and the node write uses the
RootRenderable idiom (dpl_GetDCSMatrix + assign).

Also banked: the chase-frame fifobridge variant -- camera framed on the
articulated root instead of the arena bounds -- as the offline proof harness
for any future pose question.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-29 10:02:06 -05:00
co-authored by Claude Fable 5
parent d143f833ab
commit e846717283
6 changed files with 835 additions and 38 deletions
+51 -38
View File
@@ -14,6 +14,13 @@
#if !defined(BTL4VID_HPP)
# include <btl4vid.hpp>
#endif
//
// Matrix4x4 -- the DCS wire layout (see RecurseSKLFile). l4video.hpp pulls
// rotation.hpp but not matrix.hpp; L4VIDRND.CPP includes it the same way.
//
#if !defined(MATRIX_HPP)
# include <matrix.hpp>
#endif
//
// The engine's damage-zone tagging callback reads this while geometry is
@@ -222,55 +229,61 @@ dpl_DCS *
dpl_SetDCSZone(dcs, zone);
//
// STAGE 2a -- the node's LOCAL TRANSLATION.
// The node's LOCAL TRANSFORM, in the engine's own terms -- the wire
// convention is no longer inferred from captures, it is READ from the
// engine: RootRenderable's ctor (L4VIDRND.CPP:853) writes an entity pose
// into a DCS with
//
// Slot convention, derived rather than guessed. A DCS flush body is
// [remote][type_check][node][pad][16 x float32]: our own identity
// matrices were found on the wire at body offset 16, which also
// showed analyze_scene.py's rest[4:68] read to be one word early.
// Re-reading a real BT capture with that correction gives a clean
// identity with the translation in the LAST ROW -- a row-vector
// convention, the transpose of MUNGA's AffineMatrix (which is 3x4
// row-major with translation in the 4th COLUMN, entries[3/7/11], per
// AFFNMTRX.CPP:50).
// *(Matrix4x4*)dpl_GetDCSMatrix(myDCS) = myEntity->localToWorld;
//
// Rotation is deliberately left identity for now: every base-pose
// pitch/yaw/roll in MAD.SKL is 0 or ~1e-3, so TRANSLATION alone
// assembles the model -- which isolates this one convention and
// makes a wrong guess about rotation order impossible to mistake for
// a wrong guess about translation.
// so whatever Matrix4x4::operator=(const AffineMatrix&) produces IS the
// DCS matrix layout. Reading it (MATRIX.CPP:130): Matrix4x4 is ROW-major
// (MATRIX.HPP:113, entries[(Row<<2)+Column]) with rotation in rows 0-2,
// ZEROS in column 3, and the TRANSLATION IN ROW 3 -- entries 12/13/14.
//
// (The previous revision put translation at entries 3/7/11, quoting
// AffineMatrix as precedent. AffineMatrix does keep translation at
// 3/7/11 -- but because it is COLUMN-major, entries[(column<<2)+row]
// (AFFNMTRX.HPP:99); its (3,c) translation row lands at 3/7/11 by
// storage, not by convention. Matrix4x4 transposes that on copy. The
// 'last row made the maths blow up' claim attached to the old choice
// came from the contaminated bisect -- the crashes were the RIO fault.)
//
// Rotation comes from the page's pitch/yaw/roll through the engine's own
// Matrix4x4::operator=(const EulerAngles&), so the rotation ORDER is the
// engine's by construction, not a guess. MAD.SKL base-pose angles are
// all 0 or ~1e-3, so this is nearly identity today -- but it is the
// correct compose for any skeleton authored with real angles.
//
Scalar
tran_x = 0.0f,
tran_y = 0.0f,
tran_z = 0.0f;
tran_z = 0.0f,
rot_pitch = 0.0f,
rot_yaw = 0.0f,
rot_roll = 0.0f;
skeleton->GetEntry(page_name, "tranx", &tran_x);
skeleton->GetEntry(page_name, "trany", &tran_y);
skeleton->GetEntry(page_name, "tranz", &tran_z);
skeleton->GetEntry(page_name, "pitch", &rot_pitch);
skeleton->GetEntry(page_name, "yaw", &rot_yaw);
skeleton->GetEntry(page_name, "roll", &rot_roll);
Matrix4x4
node_matrix;
node_matrix = EulerAngles(rot_pitch, rot_yaw, rot_roll);
node_matrix(3,0) = tran_x;
node_matrix(3,1) = tran_y;
node_matrix(3,2) = tran_z;
//
// Write in place and flush -- the same idiom as RootRenderable's ctor
// (dpl_GetDCSMatrix + assign), not dpl_SetDCSMatrix.
//
float32
matrix[16];
int
i;
for (i = 0; i < 16; ++i)
{
matrix[i] = 0.0f;
}
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0f;
//
// Translation goes in the 4th COLUMN (3/7/11), the same layout as
// MUNGA's own AffineMatrix (3x4 row-major, entries[3/7/11] --
// AFFNMTRX.CPP:50). Putting it in the last ROW instead made the
// matrix PROJECTIVE under a column-vector convention and the
// transform maths blew up: the walk died on the first node that
// carries geometry, reproducibly, and bisecting with the reads kept
// but the values suppressed ran clean. Same library house as
// MUNGA, same convention.
//
matrix[3] = (float32)tran_x;
matrix[7] = (float32)tran_y;
matrix[11] = (float32)tran_z;
dpl_SetDCSMatrix(dcs, matrix);
*dcs_matrix = dpl_GetDCSMatrix(dcs);
Check_Pointer(dcs_matrix);
*(Matrix4x4 *)dcs_matrix = node_matrix;
if (parent_dcs != NULL)
{
@@ -1274,3 +1274,48 @@ a boot-time state where no frame is ever outstanding.
STATUS: emulator rebuilding; verification = the same norio run drawing PAST
frame 232 with the camera tracking, then two agreeing runs before belief.
--------------------------------------------------------------------------------
THE MECH STANDS: MATRIX CONVENTION PROVEN FROM ENGINE SOURCE, CONFIRMED VISUALLY
--------------------------------------------------------------------------------
emulator/render-bridge/pose_chase.png -- the MadCat assembled from its 19
parts, standing in the arena at its live articulated position (chase render of
the live fifodump, framed on anim_abs root 0x672). Feet, legs, torso, weapon
pods, canopy plate with the purple glass panels. Not a collapsed stack.
THE CONVENTION, now read from the engine rather than inferred from captures:
RootRenderable's ctor writes an entity pose into a DCS via
*(Matrix4x4*)dpl_GetDCSMatrix(myDCS) = myEntity->localToWorld;
so Matrix4x4::operator=(const AffineMatrix&) (MATRIX.CPP:130) IS the wire
layout: Matrix4x4 is ROW-major (entries[(Row<<2)+Column], MATRIX.HPP), rotation
in rows 0-2, ZEROS in column 3, TRANSLATION IN ROW 3 = entries 12/13/14.
The previous choice (translation at 3/7/11) rested on two errors, both now
corrected in RecurseSKLFile's comments: (1) AffineMatrix DOES keep translation
at entries 3/7/11 -- but because it is COLUMN-major (entries[(column<<2)+row],
AFFNMTRX.HPP:99), that is its (3,c) translation ROW by storage; Matrix4x4
transposes on copy. Citing it as precedent for a row-major layout was reading
the storage and ignoring the indexer. (2) "translation in the last row made
the maths blow up" came from the contaminated bisect -- those crashes were the
RIO fault.
Rotation now composes through the engine's own Matrix4x4::operator=(const
EulerAngles&) from the page's pitch/yaw/roll, so the rotation order is the
engine's by construction. Node write uses the RootRenderable idiom
(dpl_GetDCSMatrix + assign), not dpl_SetDCSMatrix.
Tooling: the chase-frame variant of fifobridge (camera framed on the
articulated root instead of arena bounds) is the ten-line proof harness for
any future pose question -- offline, against a copy of the live dump, zero
interference with the running pod.
REMAINING VISUAL WORK, in order of blockingness:
* Eye height: the inside view sits at hull Y+UPOFF and stares through the
torso panels. The engine's answer is the siteeyepoint segment compose
(SetupCull) -- our skeleton has the joint chain, so the eye DCS should ride
jointcockpit/siteeyepoint rather than the root. COCKPIT-CAGE-NOTES.md
territory (glance-hide / punch cutout) applies once the height is right.
* Proportions/orientation fine-tune once a cleaner outside frame exists;
limbs may need per-joint rotation signs checked against a shipped capture.