1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! Did the output change at all?
//!
//! Every other report here checks a *property*. The validity harness checks
//! topology and would shrug at a vertex that moved; the accuracy harness checks
//! distance to the surface and would shrug at a vertex that moved by an ulp; the
//! property suite generates fresh meshes every run and has no memory. So a change
//! that is topologically identical, geometrically indistinguishable and
//! statistically invisible — a reordered summation, a different rounding on one
//! axis, a case table entry rotated onto an equivalent one — passes everything
//! and reaches a consumer's golden data as a silent diff.
//!
//! This is the function that notices, and it is public because a consumer with
//! committed golden data needs exactly the same answer this crate's own T-007
//! fixture needs. `golden.rs` calls it; so does `benches/experiment_p38.rs`,
//! which has to show that a change to a *private buffer's layout* moved no
//! output. Two copies of a hash are two answers to "did it change", so there is
//! one.
//!
//! # Why the hash is hand-rolled
//!
//! FNV-1a, ten lines, no dependency. `std`'s `DefaultHasher` is explicitly
//! documented as unstable across Rust releases, so a fixture built on it would
//! break on a toolchain bump and say "the mesher changed" — the one thing a
//! regression fixture must never lie about.
//!
//! The hash consumes raw IEEE bit patterns rather than formatted decimals, so it
//! distinguishes `+0.0` from `-0.0` and never depends on float formatting. That
//! matters here: a sign flip on a zero coordinate is exactly what a reordered
//! summation produces, and it is the class of change T-004 exists to catch.
//!
//! # Why `f64` only
//!
//! [`Real`](crate::Real) exposes no bit access — `total_cmp` compares, it does
//! not extract — so a generic hash would need `to_bits` added to a sealed core
//! trait, which this ticket does not justify. `f64` is what every existing test
//! meshes in, and it is the width where a change in the *algorithm* shows up
//! undisguised by rounding.
use crateMeshBuffer;
/// FNV-1a, 64-bit. Specified rather than chosen: the constants are the published
/// ones and the algorithm is four lines, so a reader can confirm it rather than
/// trust it.
;
/// Hash a mesh's exact contents.
///
/// Counts are hashed first, so two meshes cannot collide by one being a prefix
/// of the other — a truncated index buffer is a defect this must catch.
///
/// ```
/// use isomesh::fields::Sphere;
/// use isomesh::marching_cubes::MarchingCubes;
/// use isomesh::validate::mesh_hash;
/// use isomesh::{MeshBuffer, RuntimeShape3};
///
/// let shape = RuntimeShape3::new([17; 3])?;
/// let mut out = MeshBuffer::<f64>::new();
/// MarchingCubes::<f64>::new().extract(&Sphere::canonical(), &shape, [-2.0; 3], 0.25, &mut out)?;
///
/// // The same mesh hashes the same way, and that is the whole contract.
/// assert_eq!(mesh_hash(&out), mesh_hash(&out.clone()));
/// # Ok::<(), isomesh::Error>(())
/// ```