diff --git a/crates/fparkan-render/src/lib.rs b/crates/fparkan-render/src/lib.rs index 2463087..09d28f9 100644 --- a/crates/fparkan-render/src/lib.rs +++ b/crates/fparkan-render/src/lib.rs @@ -142,6 +142,37 @@ pub struct RawCameraPose { pub selector2: RawCameraTransform, } +/// Raw projection state observed through the original `CBufferingCamera` ABI. +/// +/// The five-float context block is intentionally represented as original words: +/// its indices are observed, but their semantic labels have not yet all been +/// recovered. The field-of-view value is the type-0 input to `tan(fov / 2)`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RawCameraProjection { + /// Viewport rectangle as `(left, top, right, bottom)`. + pub viewport: [i32; 4], + /// Original projection selector. + pub projection_type: u32, + /// Original IEEE-754 FOV value in radians. + pub field_of_view_radians_bits: u32, + /// Exact five-float context block returned by the primary renderer. + pub context_words: [u32; 5], +} + +impl RawCameraProjection { + /// Returns the type-0 FOV input in radians. + #[must_use] + pub fn field_of_view_radians(self) -> f32 { + f32::from_bits(self.field_of_view_radians_bits) + } + + /// Returns the five context values without assigning semantic labels. + #[must_use] + pub fn context_values(self) -> [f32; 5] { + self.context_words.map(f32::from_bits) + } +} + /// Immutable camera data visible to command generation. #[derive(Clone, Debug, PartialEq)] pub struct CameraSnapshot { @@ -155,6 +186,11 @@ pub struct CameraSnapshot { /// conventions have been recovered; it preserves the ABI boundary for the /// runtime adapter and deterministic captures. pub raw_pose: Option, + /// Optional unconverted source-projection state. + /// + /// This is retained independently of `projection`, because its legacy + /// context words have not yet been mapped to a Vulkan clip convention. + pub raw_projection: Option, } impl Default for CameraSnapshot { @@ -163,6 +199,7 @@ impl Default for CameraSnapshot { view: identity_transform(), projection: identity_transform(), raw_pose: None, + raw_projection: None, } } } @@ -874,6 +911,26 @@ mod tests { assert_eq!(CameraSnapshot::default().raw_pose, None); } + #[test] + fn raw_camera_projection_preserves_live_context_words_without_labeling_them() { + let projection = RawCameraProjection { + viewport: [0, 0, 1024, 768], + projection_type: 0, + field_of_view_radians_bits: 1.04_f32.to_bits(), + context_words: [ + 0.0_f32.to_bits(), + 0.5_f32.to_bits(), + 700.0_f32.to_bits(), + 0.1_f32.to_bits(), + 0.99_f32.to_bits(), + ], + }; + + assert_eq!(projection.field_of_view_radians(), 1.04); + assert_eq!(projection.context_values(), [0.0, 0.5, 700.0, 0.1, 0.99]); + assert_eq!(CameraSnapshot::default().raw_projection, None); + } + #[test] fn raw_camera_transform_inverts_only_non_singular_affine_blocks() { let source = [ diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index 2da48fd..5b28892 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -1364,6 +1364,13 @@ is only a table lookup keyed by two still-unnamed inputs. No virtual call was made by the probe. Therefore the block's values cannot yet be assigned as near/far or reused by the Vulkan adapter. +The runtime render model now retains this evidence as `RawCameraProjection` on +`CameraSnapshot`: the viewport rectangle, projection selector, exact FOV bits, +and all five context words travel through the backend-neutral boundary without +changing the current Vulkan projection. Its float accessors deliberately leave +the context fields unnamed; preserving a raw ABI value is safer than encoding a +near/far or clip-space convention before it is demonstrated. + A fresh no-input launch of the canonical `iron_3d.exe` did create a responsive window titled `Parkan. Железная Стратегия`. A read-only probe then requested `PROCESS_QUERY_INFORMATION | PROCESS_VM_READ` and attempted to read the known