feat(render): recover Iron3D Euler transform

This commit is contained in:
2026-07-18 15:28:20 +04:00
parent 438aaebcbc
commit 468ecc4e32
2 changed files with 98 additions and 0 deletions
+87
View File
@@ -246,6 +246,66 @@ impl LegacyD3d7Projection {
}
}
/// Affine placement inputs consumed by `Iron3D`'s recovered Euler-matrix builder.
///
/// This records the exact matrix construction at `iron3d.dll` RVA `0x36610`.
/// It is intentionally distinct from a mission placement contract: the static
/// trace has not yet proved that TMA's three raw orientation floats reach this
/// builder unchanged for every object category.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LegacyIron3dEulerTransform {
/// World-space translation passed to the builder.
pub translation: [f32; 3],
/// Three angles in the builder's `(x, y, z)` input order, in radians.
pub orientation_radians: [f32; 3],
}
impl LegacyIron3dEulerTransform {
/// Reconstructs the builder's finite row-major affine matrix.
///
/// The recovered x87 code evaluates `Rz(z) * Ry(y) * Rx(x)` and writes
/// translation into the last column. This uses portable `f32` trigonometry;
/// any future x87-compatibility path must be separately capture-validated.
#[must_use]
pub fn try_row_major(self) -> Option<[f32; 16]> {
if !self
.translation
.iter()
.chain(self.orientation_radians.iter())
.all(|value| value.is_finite())
{
return None;
}
let [x, y, z] = self.orientation_radians;
let (sin_x, cos_x) = x.sin_cos();
let (sin_y, cos_y) = y.sin_cos();
let (sin_z, cos_z) = z.sin_cos();
let [translation_x, translation_y, translation_z] = self.translation;
let matrix = [
cos_z * cos_y,
cos_z * sin_y * sin_x - sin_z * cos_x,
cos_z * sin_y * cos_x + sin_z * sin_x,
translation_x,
sin_z * cos_y,
sin_z * sin_y * sin_x + cos_z * cos_x,
sin_z * sin_y * cos_x - cos_z * sin_x,
translation_y,
-sin_y,
cos_y * sin_x,
cos_y * cos_x,
translation_z,
0.0,
0.0,
0.0,
1.0,
];
matrix
.iter()
.all(|value| value.is_finite())
.then_some(matrix)
}
}
/// Raw camera state observed through the original Terrain camera interface.
///
/// Selector 0 supplies the currently active transform and selector 2 supplies
@@ -1133,6 +1193,33 @@ mod tests {
);
}
#[test]
fn iron3d_euler_builder_uses_rz_ry_rx_and_last_column_translation() {
let matrix = LegacyIron3dEulerTransform {
translation: [418.103_18, 717.433, 3.040_938_9],
orientation_radians: [0.0, 0.0, std::f32::consts::FRAC_PI_2],
}
.try_row_major()
.expect("finite recovered builder inputs");
assert!((matrix[0]).abs() < 0.000_001);
assert!((matrix[1] + 1.0).abs() < 0.000_001);
assert!((matrix[4] - 1.0).abs() < 0.000_001);
assert!((matrix[5]).abs() < 0.000_001);
assert_eq!(matrix[3], 418.103_18);
assert_eq!(matrix[7], 717.433);
assert_eq!(matrix[11], 3.040_938_9);
assert_eq!(matrix[15], 1.0);
assert_eq!(
LegacyIron3dEulerTransform {
translation: [0.0, 0.0, 0.0],
orientation_radians: [f32::NAN, 0.0, 0.0],
}
.try_row_major(),
None
);
}
fn snapshot_draw(
id: u64,
phase: RenderPhase,
+11
View File
@@ -1437,6 +1437,17 @@ on its XY diagnostic projection until a runtime supplies a real legacy camera;
selecting source-world coordinates with identity camera would be a misleading
empty/off-screen viewer rather than a compatibility result.
Static analysis of GOG `iron3d.dll` now recovers an exact adjacent placement
primitive: RVA `0x36610` evaluates a row-major affine
`Rz(z) * Ry(y) * Rx(x)` and writes translation in the final column. The
backend-neutral `LegacyIron3dEulerTransform` preserves that finite portable
formula with a golden yaw-vector test. This is deliberately not wired to TMA:
AutoDemo records strongly suggest the candidate `(0, 0, z)` radians (all eight
objects have zero first two values and `z` in approximately `[-pi, pi]`), but
the current static trace has not proved that those raw record fields reach this
builder unchanged for every mission object category. The remaining link needs
a direct loader-to-builder dataflow or a dynamic placement capture.
`VulkanSmokeRenderer::set_camera` now accepts a new finite camera between frame
submissions and uses it for the next command buffer without rebuilding the
swapchain, buffers, descriptors or pipelines. `VulkanStaticCamera` also accepts