diff --git a/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json b/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json index 6cd37887..b4e47788 100644 --- a/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json +++ b/crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.json @@ -1 +1 @@ -{"artifact_id":"wcag22-srgb8-luminance-q55-v1","basis_point_proof":{"checks":30,"drop_all_semantics":"zero required surplus; current must still meet the anchor","drop_domain_inclusive":[0,10000],"nonpositive_baseline_semantics":"zero required surplus; current must meet the anchor"},"bound_id":"point-support-reference-surplus-q55-bps-v1","certified_claim":"for every successfully evaluated enabled stability cell, decision is Retained iff current_lower_surplus >= (10000-drop_bps)/10000 * max(baseline_lower_surplus,0); the declared anchor remains a separate hard floor","comparator_proof":{"algorithm":"euclidean-continued-fraction-ordering-v1","dense_denominator_inclusive":[1,31],"dense_numerator_inclusive":[0,31],"dense_small_cases":984064,"invariant":"equal integer parts; reciprocal proper fractions reverse order","largest_fibonacci_index":186,"oracle":"unbounded-integer-cross-product","random_cases":250000,"random_corpus_sha256":"97c4af7b452b31a4ab92645f70c17acb38bf57ca55484e32ad9d7d79d97a333d","random_seed":210583930,"termination":"each nonterminal denominator becomes a strictly smaller remainder","u128_adversarial_cases":190},"declared_operation_law":"q55-lower-reference-distance-explicit-anchor-bps-retention-v1","excluded_claim":"does not certify retention against the unknown exact baseline surplus, renderer equivalence outside encoded-sRGB8 source-over, or a successful result when evaluation fails","integer_replay_envelope":{"assumption":"every Q55 luminance upper <= scale + 3","i128_max":170141183460469231731687303715884105727,"offset_cleared_denominator_max":756604737398243388,"positive_baseline_numerator_max":1188950301625811064,"rational_denominator_max":1513209474796486776,"required_denominator_max":15132094747964867760000,"required_numerator_max":11889503016258110640000,"signed_anchor_abs_coarse_max":5296233161787703716,"u128_max":340282366920938463463374607431768211455,"u64_max":18446744073709551615},"profile_id":"srgb8-q55-retained-reference-surplus-bps-v1","proof_id":"point-support-reference-surplus-integer-v1","proof_payload_sha256":"78669986ea1a7a75f40e76c19beba4ff9c72abcbf1a240d4437846fc8d28519c","q55_dependency":{"artifact_id":"wcag22-srgb8-luminance-q55-v1","artifact_sha256":"7ff239d9052b346f3c50da01ca65ca2330892ed1a3ff30e190797fcef6f03604","maximum_luminance_upper":36028797018963971,"outward_interval_width_bound":3,"proof_id":"wcag22-srgb8-full-domain-q55-v1","proof_payload_sha256":"3c639a7c875046c46b56b51ecdd67d5ecaf14a1134490c88a222e7037b63c0f2","proof_sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd","q55_scale":36028797018963968},"reference_and_anchor_proof":{"anchor_identity_checks":75,"orientation_law":"distance-magnitude-symmetric-orientation-reported-separately","overlap_lower_distance":"0/1","separated_endpoint_checks":504},"schema_version":2,"site_id":"point-support-retained-reference-surplus-v1","source_binding_exclusions":["whole-crate compilation or compiler/toolchain attestation","binary, package, FFI, renderer, or browser transport attestation","unrelated Lab Colors modules outside the declared point-support semantic cone"],"source_binding_law":"point-support-rust-whole-file-semantic-cone-v2","source_binding_schema_version":2,"source_binding_scope":"exact bytes of the private point-support Rust semantic cone and its two WCAG include_str inputs; comments and cfg(test) text are intentionally significant","source_closure_sha256":"246c77dbf4923aca4cdaedb12be910ceb2885c94e4e42f652cdcba9b9417a427","source_files":[{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-q55-proof-v1.json","sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"},{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-v1.json","sha256":"b4bb7e5f17a99f2c911fdbe3da23a48b049277b796291094950f14680cc3cc7b"},{"kind":"rust-source","path":"crates/labcolors-core/src/appearance.rs","sha256":"455f94bdc0064765214e21ec38e49939e9ebbf765d18bb709512f7210c986953"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"195a67327a3bd86d7816b634481389930bf68577bb1202fad14c2ea152df8625"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/exact.rs","sha256":"892576a8621185352583e63dc0a1aacac32e32a8063b6fe24ae16d4ff9dce7cb"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/mod.rs","sha256":"3a4c2911781f91c91c12fc23929843865a8e41d5630f4df41f77130434b5f228"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/wcag22.rs","sha256":"40f288b222fbd102970916437d3f99c33ec2dfb77041b1c8361d27f08ff018ca"},{"kind":"rust-source","path":"crates/labcolors-core/src/hash.rs","sha256":"f97a0fd7d6ad3162f0f1dfb326fccfb7ed40da9a8fa67a5b8a239a1ae2ae49c3"},{"kind":"rust-source","path":"crates/labcolors-core/src/lcs_occurrence.rs","sha256":"9ad998d3b7ac01a03afa398a6750ab71dc1278da991202933cf9006c3cedf5f5"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"b3edb3764119c0b4fd50f52b62cbc07fa81c4bfdf1246b91f20eb3d8d4eebd31"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"f1d6c7a66885326caea2f7f469061c723b826ff99b294324e5a478724f8981f6"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"0755210e3e591d7049f293a0f0b7647681631f32feee5ad7d3b3309cffca8f9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"6cd73600267b148c3e9ecc8d2d623f7f8576aed0e1c4c7c50071e297810b8d4b"},{"kind":"rust-source","path":"crates/labcolors-core/src/srgb8.rs","sha256":"6c95324eb05476f35f75375a9af0b2b4a41b8b2978c46e67d2ce1aea5adde342"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22.rs","sha256":"7ba7864eb7e73789bad6c63c64a4dc2dcc08c2da6921375fb9564fca230c2780"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/kernel.rs","sha256":"c97980c1ca2c7ea9cabff9c8d2fb7282773cca180ae15948391c29c9d6196040"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/q55_data.rs","sha256":"af4d23d6b70c45ce6efa839e7dda4bb0a61f6aae43cb805af6fa9b29e6c3bae2"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22_evidence.rs","sha256":"3c5a75b07254c6071a64700af208a64987d0f0ea9698eadc54a9e74585ce1f72"}],"source_negative_controls":43,"universal_algebraic_certificate":{"basis_point_scale_instantiation":10000,"domain":"integers; Q55 scale Q>0; anchor L>=D>=0; lighter monotonicity L2>=L1>D>=0; darker monotonicity L>D2>=D1>=0; current/baseline denominators b,q>0; basis-point scale B>0 instantiated as 10000; p>0; a>=0; 0<=drop_bps<=B","identities":["three explicit anchor-surplus formulas after denominator clearing","reference distance is monotone increasing in lighter L","reference distance is monotone decreasing in darker D","positive-baseline retained threshold is p*(B-drop)/(q*B)","a/b >= p*(B-drop)/(q*B) iff a*q*B >= p*(B-drop)*b"],"method":"exact-sparse-integer-polynomial-identities-plus-positive-denominator-order-lemma-v1","nonpositive_baseline_case":"max(baseline,0)=0; retained threshold is exactly zero","symbolic_mutation_controls":{"anchor_coefficients_and_denominator":6,"retained_cross_product":5},"wolfram_language_cross_check":{"query":"FullSimplify[{20 g/d - 0 == 20 g/d, 20 g/d - 2 == (20 g - 2 d)/d, 20 g/d - 7/2 == (40 g - 7 d)/(2 d), Equivalent[a/b >= p (s-x)/(q s), a q s >= p (s-x) b], Max[p/q, 0] (s-x)/s == Piecewise[{{0, p <= 0}}, p (s-x)/(q s)]}, Assumptions -> Element[{a,b,p,q,s,x,g,d}, Integers] && a >= 0 && b > 0 && q > 0 && s > 0 && 0 <= x <= s && d > 0 && g >= 0]","query_sha256":"8cdbb9964583030c8b92498961896cb2a98613f1cb31eb7c54acdf8e16beff10","result":"{True, True, True, True, True}","result_sha256":"13a8f2ee8d0fde335a638e46d7cc8a8427b9a1437c77d22cfcf925bb87fa6303"}},"verifier_sha256":"3c067cb4ce2af0d9ff514ba0937650ebd62514bb8d6bafb7fc1bc319e0e1816a"} +{"artifact_id":"wcag22-srgb8-luminance-q55-v1","basis_point_proof":{"checks":30,"drop_all_semantics":"zero required surplus; current must still meet the anchor","drop_domain_inclusive":[0,10000],"nonpositive_baseline_semantics":"zero required surplus; current must meet the anchor"},"bound_id":"point-support-reference-surplus-q55-bps-v1","certified_claim":"for every successfully evaluated enabled stability cell, decision is Retained iff current_lower_surplus >= (10000-drop_bps)/10000 * max(baseline_lower_surplus,0); the declared anchor remains a separate hard floor","comparator_proof":{"algorithm":"euclidean-continued-fraction-ordering-v1","dense_denominator_inclusive":[1,31],"dense_numerator_inclusive":[0,31],"dense_small_cases":984064,"invariant":"equal integer parts; reciprocal proper fractions reverse order","largest_fibonacci_index":186,"oracle":"unbounded-integer-cross-product","random_cases":250000,"random_corpus_sha256":"97c4af7b452b31a4ab92645f70c17acb38bf57ca55484e32ad9d7d79d97a333d","random_seed":210583930,"termination":"each nonterminal denominator becomes a strictly smaller remainder","u128_adversarial_cases":190},"declared_operation_law":"q55-lower-reference-distance-explicit-anchor-bps-retention-v1","excluded_claim":"does not certify retention against the unknown exact baseline surplus, renderer equivalence outside encoded-sRGB8 source-over, or a successful result when evaluation fails","integer_replay_envelope":{"assumption":"every Q55 luminance upper <= scale + 3","i128_max":170141183460469231731687303715884105727,"offset_cleared_denominator_max":756604737398243388,"positive_baseline_numerator_max":1188950301625811064,"rational_denominator_max":1513209474796486776,"required_denominator_max":15132094747964867760000,"required_numerator_max":11889503016258110640000,"signed_anchor_abs_coarse_max":5296233161787703716,"u128_max":340282366920938463463374607431768211455,"u64_max":18446744073709551615},"profile_id":"srgb8-q55-retained-reference-surplus-bps-v1","proof_id":"point-support-reference-surplus-integer-v1","proof_payload_sha256":"491f909d707f002a8fe2b6b04337ba6f7d044fd5e2b85585a8c978feb930b36c","q55_dependency":{"artifact_id":"wcag22-srgb8-luminance-q55-v1","artifact_sha256":"7ff239d9052b346f3c50da01ca65ca2330892ed1a3ff30e190797fcef6f03604","maximum_luminance_upper":36028797018963971,"outward_interval_width_bound":3,"proof_id":"wcag22-srgb8-full-domain-q55-v1","proof_payload_sha256":"3c639a7c875046c46b56b51ecdd67d5ecaf14a1134490c88a222e7037b63c0f2","proof_sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd","q55_scale":36028797018963968},"reference_and_anchor_proof":{"anchor_identity_checks":75,"orientation_law":"distance-magnitude-symmetric-orientation-reported-separately","overlap_lower_distance":"0/1","separated_endpoint_checks":504},"schema_version":2,"site_id":"point-support-retained-reference-surplus-v1","source_binding_exclusions":["whole-crate compilation or compiler/toolchain attestation","binary, package, FFI, renderer, or browser transport attestation","unrelated Lab Colors modules outside the declared point-support semantic cone"],"source_binding_law":"point-support-rust-whole-file-semantic-cone-v2","source_binding_schema_version":2,"source_binding_scope":"exact bytes of the private point-support Rust semantic cone and its two WCAG include_str inputs; comments and cfg(test) text are intentionally significant","source_closure_sha256":"b25636d4ae969d4a93a82a1324fb4445ba4861a51f18a85c4a0a062afcfbbfda","source_files":[{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-q55-proof-v1.json","sha256":"ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"},{"kind":"compile-time-input","path":"crates/labcolors-core/contracts/wcag22-srgb8-v1.json","sha256":"b4bb7e5f17a99f2c911fdbe3da23a48b049277b796291094950f14680cc3cc7b"},{"kind":"rust-source","path":"crates/labcolors-core/src/appearance.rs","sha256":"09be54900efe29ffdac8705efd0d6d613055c90d634446ca4b228f51a63997d0"},{"kind":"rust-source","path":"crates/labcolors-core/src/composition.rs","sha256":"195a67327a3bd86d7816b634481389930bf68577bb1202fad14c2ea152df8625"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/exact.rs","sha256":"892576a8621185352583e63dc0a1aacac32e32a8063b6fe24ae16d4ff9dce7cb"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/mod.rs","sha256":"e73b9c0b8c3a4112cb53987753d5a6b5f639774b0b872afaea9836e6647a2f7d"},{"kind":"rust-source","path":"crates/labcolors-core/src/constraints/wcag22.rs","sha256":"856093c91159d8b3faab001f2d6524d33d7b16458a5a4e98ea65f8c62ab2694c"},{"kind":"rust-source","path":"crates/labcolors-core/src/hash.rs","sha256":"f97a0fd7d6ad3162f0f1dfb326fccfb7ed40da9a8fa67a5b8a239a1ae2ae49c3"},{"kind":"rust-source","path":"crates/labcolors-core/src/lcs_occurrence.rs","sha256":"6f202ad7425a235b9d18caba0c817fc33a2b8e042050a34f5ddff3fd09efc53d"},{"kind":"rust-source","path":"crates/labcolors-core/src/lib.rs","sha256":"b3edb3764119c0b4fd50f52b62cbc07fa81c4bfdf1246b91f20eb3d8d4eebd31"},{"kind":"rust-source","path":"crates/labcolors-core/src/numerics.rs","sha256":"e73a12136494f2ef9aca4e943ab38302c1439f054cecab36a552d35252c164f9"},{"kind":"rust-source","path":"crates/labcolors-core/src/observation.rs","sha256":"0fa29533dab84d51af7993bb04dbef7fb978ca55499ea5e1ade282cbc84e6818"},{"kind":"rust-source","path":"crates/labcolors-core/src/point_support.rs","sha256":"0755210e3e591d7049f293a0f0b7647681631f32feee5ad7d3b3309cffca8f9d"},{"kind":"rust-source","path":"crates/labcolors-core/src/session.rs","sha256":"6cd73600267b148c3e9ecc8d2d623f7f8576aed0e1c4c7c50071e297810b8d4b"},{"kind":"rust-source","path":"crates/labcolors-core/src/srgb8.rs","sha256":"6c95324eb05476f35f75375a9af0b2b4a41b8b2978c46e67d2ce1aea5adde342"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22.rs","sha256":"7ba7864eb7e73789bad6c63c64a4dc2dcc08c2da6921375fb9564fca230c2780"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/kernel.rs","sha256":"c97980c1ca2c7ea9cabff9c8d2fb7282773cca180ae15948391c29c9d6196040"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22/q55_data.rs","sha256":"af4d23d6b70c45ce6efa839e7dda4bb0a61f6aae43cb805af6fa9b29e6c3bae2"},{"kind":"rust-source","path":"crates/labcolors-core/src/wcag22_evidence.rs","sha256":"3c5a75b07254c6071a64700af208a64987d0f0ea9698eadc54a9e74585ce1f72"}],"source_negative_controls":43,"universal_algebraic_certificate":{"basis_point_scale_instantiation":10000,"domain":"integers; Q55 scale Q>0; anchor L>=D>=0; lighter monotonicity L2>=L1>D>=0; darker monotonicity L>D2>=D1>=0; current/baseline denominators b,q>0; basis-point scale B>0 instantiated as 10000; p>0; a>=0; 0<=drop_bps<=B","identities":["three explicit anchor-surplus formulas after denominator clearing","reference distance is monotone increasing in lighter L","reference distance is monotone decreasing in darker D","positive-baseline retained threshold is p*(B-drop)/(q*B)","a/b >= p*(B-drop)/(q*B) iff a*q*B >= p*(B-drop)*b"],"method":"exact-sparse-integer-polynomial-identities-plus-positive-denominator-order-lemma-v1","nonpositive_baseline_case":"max(baseline,0)=0; retained threshold is exactly zero","symbolic_mutation_controls":{"anchor_coefficients_and_denominator":6,"retained_cross_product":5},"wolfram_language_cross_check":{"query":"FullSimplify[{20 g/d - 0 == 20 g/d, 20 g/d - 2 == (20 g - 2 d)/d, 20 g/d - 7/2 == (40 g - 7 d)/(2 d), Equivalent[a/b >= p (s-x)/(q s), a q s >= p (s-x) b], Max[p/q, 0] (s-x)/s == Piecewise[{{0, p <= 0}}, p (s-x)/(q s)]}, Assumptions -> Element[{a,b,p,q,s,x,g,d}, Integers] && a >= 0 && b > 0 && q > 0 && s > 0 && 0 <= x <= s && d > 0 && g >= 0]","query_sha256":"8cdbb9964583030c8b92498961896cb2a98613f1cb31eb7c54acdf8e16beff10","result":"{True, True, True, True, True}","result_sha256":"13a8f2ee8d0fde335a638e46d7cc8a8427b9a1437c77d22cfcf925bb87fa6303"}},"verifier_sha256":"16c7fa1a3b1dee795b4434329ebf329066468eeb309213960e35698255b0dd9c"} diff --git a/crates/labcolors-core/src/appearance.rs b/crates/labcolors-core/src/appearance.rs index 8c635e94..321a3709 100644 --- a/crates/labcolors-core/src/appearance.rs +++ b/crates/labcolors-core/src/appearance.rs @@ -134,17 +134,14 @@ pub(crate) struct ProgramOccurrenceBindingV1 { } impl ProgramOccurrenceBindingV1 { - #[cfg(test)] pub(crate) const fn occurrence(self) -> OccurrenceId { self.occurrence } - #[cfg(test)] pub(crate) const fn subject(self) -> PaintId { self.subject } - #[cfg(test)] pub(crate) const fn backdrop_surface(self) -> SurfaceId { self.backdrop_surface } @@ -1217,7 +1214,6 @@ impl EncodedPointPaintV1 { self.opacity } - #[cfg(test)] pub(crate) const fn opacity_bits(self) -> u64 { self.opacity.bits() } @@ -1242,7 +1238,6 @@ impl SourceOverCertificateV1 { .composite(self.subject_rgb, self.subject_opacity, self.backdrop_rgb) } - #[cfg(test)] pub(crate) const fn profile(&self) -> CompositionProfileV1 { self.profile } @@ -1357,11 +1352,11 @@ pub(crate) struct VisiblePointBindingV1 { } impl VisiblePointBindingV1 { - pub(crate) fn program_occurrence(self) -> ProgramOccurrenceBindingV1 { + pub(crate) const fn program_occurrence(self) -> ProgramOccurrenceBindingV1 { self.program_occurrence } - pub(crate) fn occurrence(self) -> SourceOverCertificateV1 { + pub(crate) const fn occurrence(self) -> SourceOverCertificateV1 { self.occurrence } diff --git a/crates/labcolors-core/src/constraints/mod.rs b/crates/labcolors-core/src/constraints/mod.rs index 4aae4880..fb887337 100644 --- a/crates/labcolors-core/src/constraints/mod.rs +++ b/crates/labcolors-core/src/constraints/mod.rs @@ -155,7 +155,6 @@ impl &self.invocation } - #[cfg(test)] pub(crate) fn measurement(&self) -> &Measurement { &self.measurement } diff --git a/crates/labcolors-core/src/constraints/wcag22.rs b/crates/labcolors-core/src/constraints/wcag22.rs index c4b28a87..cc9f3a78 100644 --- a/crates/labcolors-core/src/constraints/wcag22.rs +++ b/crates/labcolors-core/src/constraints/wcag22.rs @@ -30,7 +30,6 @@ pub(crate) struct ApplicableWcag22MeasurementV1 { evidence: NumericalDecisionEvidenceV1, } -#[cfg(test)] impl ApplicableWcag22MeasurementV1 { pub(crate) const fn profile_id(&self) -> Wcag22ProfileIdV1 { self.profile_id @@ -44,6 +43,7 @@ impl ApplicableWcag22MeasurementV1 { &self.measurement } + #[cfg(test)] pub(crate) const fn decision(&self) -> Wcag22ApplicableDecisionV1 { self.decision } diff --git a/crates/labcolors-core/src/lcs_occurrence.rs b/crates/labcolors-core/src/lcs_occurrence.rs index ea6a9295..fb7f08c5 100644 --- a/crates/labcolors-core/src/lcs_occurrence.rs +++ b/crates/labcolors-core/src/lcs_occurrence.rs @@ -28,6 +28,13 @@ pub struct ColorSignal { output_profile: OutputProfileId, } +/// Exhaustive internal decomposition for boundaries that must preserve the +/// signal profile instead of treating encoded bytes as self-describing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ColorSignalViewV1 { + Iec61966Srgb8D65(Srgb8), +} + impl ColorSignal { /// Form the only admitted encoded signal without accepting a free-form /// channel/profile pairing. @@ -45,6 +52,12 @@ impl ColorSignal { pub(crate) const fn output_profile(self) -> OutputProfileId { self.output_profile } + + pub(crate) const fn view(self) -> ColorSignalViewV1 { + match self.output_profile { + OutputProfileId::Iec61966Srgb8D65V1 => ColorSignalViewV1::Iec61966Srgb8D65(self.srgb8), + } + } } /// Exact code release for one colorimetric signal-to-tristimulus transform. @@ -321,6 +334,8 @@ fn derive_sample_with_binding( signal: ColorSignal, binding: AdmittedSrgb8TristimulusBindingV1, ) -> Result { + #[cfg(test)] + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(calls.get() + 1)); let xyz = match ( signal.output_profile(), binding.signal_output_profile(), @@ -337,9 +352,9 @@ fn derive_sample_with_binding( #[cfg(test)] thread_local! { - /// Per-thread count of modeled signal-to-tristimulus derivations. Program - /// regression tests use this deterministic metric to pin one derivation - /// per unique target occurrence and physical case without timing noise. + /// Per-thread count of modeled signal-to-tristimulus kernel executions. + /// Counting below both initial derivation and replay keeps a projection + /// from hiding recomputation behind the replay API. pub(crate) static MODELED_TRISTIMULUS_DERIVATION_CALLS: std::cell::Cell = const { std::cell::Cell::new(0) }; } @@ -352,8 +367,6 @@ thread_local! { pub(crate) fn derive_modeled_tristimulus_v1( signal: ColorSignal, ) -> Result { - #[cfg(test)] - MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(calls.get() + 1)); let binding = admitted_binding(signal.output_profile()); let sample = derive_sample_with_binding(signal, binding)?; Ok(ModeledTristimulusDerivationV1 { diff --git a/crates/labcolors-core/src/observation.rs b/crates/labcolors-core/src/observation.rs index 8881c4c8..d2549f3c 100644 --- a/crates/labcolors-core/src/observation.rs +++ b/crates/labcolors-core/src/observation.rs @@ -30,6 +30,10 @@ impl ObservationStreamId { pub(crate) const fn new(raw: u32) -> Self { Self(raw) } + + pub(crate) const fn value(self) -> u32 { + self.0 + } } /// Monotonic revision inside one [`ObservationStreamId`]. @@ -54,6 +58,10 @@ impl ScenarioId { pub(crate) const fn new(raw: u32) -> Self { Self(raw) } + + pub(crate) const fn value(self) -> u32 { + self.0 + } } /// Opaque reason why the current observation is unavailable. diff --git a/crates/labcolors-core/src/package_bridge.rs b/crates/labcolors-core/src/package_bridge.rs index 16666bba..7e594b19 100644 --- a/crates/labcolors-core/src/package_bridge.rs +++ b/crates/labcolors-core/src/package_bridge.rs @@ -12,12 +12,19 @@ use core::slice; use crate::Srgb8; use crate::appearance::{OccurrenceId, OpacityInputId, PaintId, SurfaceId, SurfaceInputPortId}; +use crate::composition::CompositionProfileV1; +use crate::constraints::{ + ExactSrgb8IdentityV1, ProgramVisiblePointBindingV1, ProgramVisiblePointPassEvidence, + ProgramVisiblePointViolationEvidence, Wcag22Srgb8V1, +}; use crate::joint::FiniteJointOrderErrorV1; use crate::lcs_occurrence::{ - AdaptingLuminanceCdM2, AppearanceContextDomainErrorV1, AppearanceContextFieldV1, - AppearanceContextId, AppearanceContextSchemaReleaseId, BackgroundLuminanceRatio, ColorSignal, - IEC_SRGB_D65_XYZ_FRAME_V1, NumericDomainError, SurroundProfileId, + AdaptingLuminanceCdM2, AdmittedSrgb8TristimulusBindingV1, AppearanceContextDomainErrorV1, + AppearanceContextFieldV1, AppearanceContextId, AppearanceContextSchemaReleaseId, + BackgroundLuminanceRatio, ColorSignal, ColorSignalViewV1, IEC_SRGB_D65_XYZ_FRAME_V1, + NumericDomainError, SurroundProfileId, }; +use crate::numerics::NumericalDecisionEvidenceV1; use crate::observation::{ ObservationError, ObservationPayloadInput, ObservationStreamId, ObservationUpdateInput, Revision, ScenarioId, SchemaOrderedScenarioSourceV1, UnknownReasonId, @@ -25,14 +32,16 @@ use crate::observation::{ use crate::program_session::{ CompiledCoreProgramV1, CompositionProfile, ConstraintId, ConstraintInvocation, CoreProgramConstraintInvocationV1, CoreProgramDraftErrorV1, CoreProgramDraftV1, - CoreProgramEvaluatorErrorV1, CoreProgramEvaluatorsV1, DeclaredJointSelectionV1, - JointCandidateStateV1, Occurrence, OpacityInput, OutputBinding, OutputSlotId, Paint, - ProgramCompileError, ProgramConflictV1, ProgramOutputV1, ProgramSessionEvaluationError, - ProgramSessionInstantiateError, ProgramSessionPlan, ProgramVerifiedV1, Source, SourceId, - Surface, Target, TargetCandidateChoiceV1, TargetCandidateId, TargetCandidateV1, TargetId, + CoreProgramEvaluatorErrorV1, CoreProgramEvaluatorsV1, CoreProgramPassEvidenceV1, + CoreProgramViolationEvidenceV1, DeclaredJointSelectionV1, JointCandidateStateV1, Occurrence, + OpacityInput, OutputBinding, OutputSlotId, Paint, ProgramCompileError, ProgramConflictV1, + ProgramConstraintCellV1, ProgramConstraintResultV1, ProgramContentIdentityV1, ProgramOutputV1, + ProgramSessionEvaluationError, ProgramSessionInstantiateError, ProgramSessionPlan, + ProgramVerifiedV1, Source, SourceId, Surface, Target, TargetCandidateChoiceV1, + TargetCandidateId, TargetCandidateV1, TargetId, }; use crate::session::{Session, SessionState, SessionUpdateError}; -use crate::wcag22::Wcag22CriterionV1; +use crate::wcag22::{Wcag22CriterionV1, Wcag22LuminanceBoundsQ55V1, Wcag22ProfileIdV1}; type CoreVerifiedV1 = ProgramVerifiedV1; type CoreConflictV1 = ProgramConflictV1; @@ -40,6 +49,11 @@ type CoreProgramPlanV1 = ProgramSessionPlan; type CoreProgramSessionV1 = Session; type CoreProgramStateV1 = SessionState; type CoreProgramPlanErrorV1 = ProgramSessionEvaluationError; +type CoreProgramConstraintCellV1 = ProgramConstraintCellV1; +type CoreExactPassEvidenceV1 = ProgramVisiblePointPassEvidence; +type CoreExactViolationEvidenceV1 = ProgramVisiblePointViolationEvidence; +type CoreWcag22PassEvidenceV1 = ProgramVisiblePointPassEvidence; +type CoreWcag22ViolationEvidenceV1 = ProgramVisiblePointViolationEvidence; macro_rules! package_program_id { ($name:ident, $core:ty) => { @@ -74,6 +88,31 @@ macro_rules! package_program_id { }; } +macro_rules! package_program_projected_id { + ($name:ident, $core:ty) => { + #[repr(transparent)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[must_use] + pub struct $name($core); + + impl $name { + const fn from_core(value: $core) -> Self { + Self(value) + } + + pub const fn value(self) -> u32 { + self.0.value() + } + } + + impl core::hash::Hash for $name { + fn hash(&self, state: &mut H) { + core::hash::Hash::hash(&self.value(), state); + } + } + }; +} + package_program_id!(PackageProgramSourceIdV1, SourceId); package_program_id!(PackageProgramTargetIdV1, TargetId); package_program_id!(PackageProgramTargetCandidateIdV1, TargetCandidateId); @@ -84,6 +123,8 @@ package_program_id!(PackageProgramSurfaceIdV1, SurfaceId); package_program_id!(PackageProgramOccurrenceIdV1, OccurrenceId); package_program_id!(PackageProgramConstraintIdV1, ConstraintId); package_program_id!(PackageProgramOutputSlotIdV1, OutputSlotId); +package_program_projected_id!(PackageProgramStreamIdV1, ObservationStreamId); +package_program_projected_id!(PackageProgramScenarioIdV1, ScenarioId); /// One finite candidate, stored as the actual Core target-candidate IR node. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -244,6 +285,22 @@ impl PackageProgramAppearanceContextV1 { surround.into_core(), ))) } + + pub fn adapting_luminance_cd_m2(self) -> f64 { + self.0.adapting_luminance_cd_m2() + } + + pub fn background_luminance_ratio_yb_yw(self) -> f64 { + self.0.background_luminance_ratio() + } + + pub const fn surround(self) -> PackageProgramSurroundV1 { + match self.0.surround_profile() { + SurroundProfileId::AverageV1 => PackageProgramSurroundV1::Average, + SurroundProfileId::DimV1 => PackageProgramSurroundV1::Dim, + SurroundProfileId::DarkV1 => PackageProgramSurroundV1::Dark, + } + } } /// Closed compile classification; the generic Core error never escapes. @@ -1168,7 +1225,7 @@ impl<'a> PackageProgramStateViewV1<'a> { /// Core-owned certificates in canonical same-call ordinal order. pub fn certificates( self, - ) -> impl ExactSizeIterator> + 'a { + ) -> impl ExactSizeIterator> + FusedIterator + 'a { let (first, second) = match self.state { SessionState::Waiting => (None, None), SessionState::Ready { current } | SessionState::Stale { previous: current } => { @@ -1183,7 +1240,9 @@ impl<'a> PackageProgramStateViewV1<'a> { } /// Total canonical output projection for this lifecycle state. - pub fn operations(self) -> impl ExactSizeIterator + 'a { + pub fn operations( + self, + ) -> impl ExactSizeIterator> + FusedIterator + 'a { let inner = match self.state { SessionState::Waiting => PackageProgramOperationSourceV1::Empty, SessionState::Ready { current } => { @@ -1195,17 +1254,21 @@ impl<'a> PackageProgramStateViewV1<'a> { .zip(self.output_slots) .all(|(output, slot)| output.output().value() == slot.value()) ); - PackageProgramOperationSourceV1::Set(current.outputs().iter()) + PackageProgramOperationSourceV1::Set { + outputs: current.outputs().iter(), + certificate: PackageProgramVerifiedCertificateV1 { inner: current }, + } } - SessionState::Stale { .. } => PackageProgramOperationSourceV1::Hold { + SessionState::Stale { previous } => PackageProgramOperationSourceV1::Hold { slots: self.output_slots.iter(), - certificate_index: 0, + certificate: PackageProgramVerifiedCertificateV1 { inner: previous }, }, SessionState::Failed { - previous: Some(_), .. + previous: Some(previous), + .. } => PackageProgramOperationSourceV1::Hold { slots: self.output_slots.iter(), - certificate_index: 1, + certificate: PackageProgramVerifiedCertificateV1 { inner: previous }, }, SessionState::Failed { previous: None, .. } => { PackageProgramOperationSourceV1::Remove(self.output_slots.iter()) @@ -1215,91 +1278,661 @@ impl<'a> PackageProgramStateViewV1<'a> { } } -/// Opaque certificate family; evaluator-specific evidence never escapes. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageProgramCertificateKindV1 { - Verified, - Conflict, +/// Collision-resistant address of the canonical physical Program content. +/// It deliberately does not identify an owner epoch or runtime authority. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PackageProgramContentIdentityV1([u8; 32]); + +impl PackageProgramContentIdentityV1 { + const fn from_core(value: ProgramContentIdentityV1) -> Self { + Self(*value.as_bytes()) + } + + pub const fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } } +/// All hard cells passed over the complete admitted physical support. #[derive(Clone, Copy)] -enum PackageProgramCertificateRefV1<'a> { - Verified(&'a CoreVerifiedV1), - Conflict(&'a CoreConflictV1), +pub struct PackageProgramVerifiedCertificateV1<'a> { + inner: &'a CoreVerifiedV1, } -/// Borrowed opaque handle to one Core-owned certificate. +impl<'a> PackageProgramVerifiedCertificateV1<'a> { + pub const fn content_identity(self) -> PackageProgramContentIdentityV1 { + PackageProgramContentIdentityV1::from_core(self.inner.report().content_identity()) + } + + pub const fn observation(self) -> PackageProgramObservationV1<'a> { + PackageProgramObservationV1 { + inner: self.inner.report().observation(), + } + } + + pub const fn selected_state_index(self) -> Option { + self.inner.selected_state_index() + } + + pub fn cells( + self, + ) -> impl ExactSizeIterator> + FusedIterator + 'a { + self.inner + .report() + .cells() + .iter() + .map(PackageProgramVerifiedCellV1::from_core) + } + + pub fn outputs( + self, + ) -> impl ExactSizeIterator> + FusedIterator + 'a + { + self.inner + .outputs() + .iter() + .map(PackageProgramCertifiedOutputV1::from_core) + } +} + +/// Exhaustive proof that every declared candidate state violates a hard cell. #[derive(Clone, Copy)] -pub struct PackageProgramCertificateV1<'a> { - inner: PackageProgramCertificateRefV1<'a>, +pub struct PackageProgramConflictCertificateV1<'a> { + inner: &'a CoreConflictV1, +} + +impl<'a> PackageProgramConflictCertificateV1<'a> { + pub const fn content_identity(self) -> PackageProgramContentIdentityV1 { + PackageProgramContentIdentityV1::from_core(self.inner.report().content_identity()) + } + + pub const fn observation(self) -> PackageProgramObservationV1<'a> { + PackageProgramObservationV1 { + inner: self.inner.report().observation(), + } + } + + pub const fn considered_state_count(self) -> usize { + self.inner.considered_state_count() + } + + pub fn cells( + self, + ) -> impl ExactSizeIterator> + FusedIterator + 'a { + self.inner + .report() + .cells() + .iter() + .map(PackageProgramConflictCellV1::from_core) + } +} + +/// Closed borrowed projection of one exact Core-owned certificate. +#[derive(Clone, Copy)] +pub enum PackageProgramCertificateV1<'a> { + Verified(PackageProgramVerifiedCertificateV1<'a>), + Conflict(PackageProgramConflictCertificateV1<'a>), } impl<'a> PackageProgramCertificateV1<'a> { const fn verified(value: &'a CoreVerifiedV1) -> Self { - Self { - inner: PackageProgramCertificateRefV1::Verified(value), - } + Self::Verified(PackageProgramVerifiedCertificateV1 { inner: value }) } const fn conflict(value: &'a CoreConflictV1) -> Self { - Self { - inner: PackageProgramCertificateRefV1::Conflict(value), + Self::Conflict(PackageProgramConflictCertificateV1 { inner: value }) + } + + pub const fn content_identity(self) -> PackageProgramContentIdentityV1 { + match self { + Self::Verified(value) => value.content_identity(), + Self::Conflict(value) => value.content_identity(), + } + } + + pub const fn observation(self) -> PackageProgramObservationV1<'a> { + match self { + Self::Verified(value) => value.observation(), + Self::Conflict(value) => value.observation(), + } + } + + #[cfg(test)] + pub(crate) fn observation_backing_ptr_for_test(self) -> *const () { + self.observation().inner.backing_ptr_for_test() + } +} + +/// The exact revision-bound observation retained by a certificate. +#[derive(Clone, Copy)] +pub struct PackageProgramObservationV1<'a> { + inner: &'a crate::observation::RevisionBoundObservationV1, +} + +impl<'a> PackageProgramObservationV1<'a> { + pub const fn stream(self) -> PackageProgramStreamIdV1 { + PackageProgramStreamIdV1::from_core(self.inner.stream()) + } + + pub const fn revision(self) -> u64 { + self.inner.revision().value() + } + + /// Canonical schema shared by every physical case. Position `i` is the + /// identity of position `i` in each [`PackageProgramPhysicalCaseV1::values`] + /// iterator; the two exact-size iterators always have equal length. + pub fn surface_input_ports( + self, + ) -> impl ExactSizeIterator + FusedIterator + 'a + { + self.inner + .schema() + .iter() + .copied() + .map(PackageProgramSurfaceInputPortIdV1::from_core) + } + + /// Canonical unique physical value vectors. Each case is schema-ordered by + /// [`Self::surface_input_ports`]; scenario IDs remain in `provenance()`. + pub fn physical_cases( + self, + ) -> impl ExactSizeIterator> + FusedIterator + 'a { + (0..self.inner.physical_case_count()).map(move |index| PackageProgramPhysicalCaseV1 { + observation: self.inner, + index, + }) + } +} + +/// Closed encoded signal family retained in a physical observation case. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PackageProgramSignalV1 { + Iec61966Srgb8D65(Srgb8), +} + +/// One canonical physical observation case and its complete provenance set. +#[derive(Clone, Copy)] +pub struct PackageProgramPhysicalCaseV1<'a> { + observation: &'a crate::observation::RevisionBoundObservationV1, + index: usize, +} + +impl<'a> PackageProgramPhysicalCaseV1<'a> { + pub fn values( + self, + ) -> impl ExactSizeIterator + FusedIterator + 'a { + self.observation + .physical_values(self.index) + .expect("package case originates from the same observation") + .iter() + .copied() + .map(|signal| match signal.view() { + ColorSignalViewV1::Iec61966Srgb8D65(value) => { + PackageProgramSignalV1::Iec61966Srgb8D65(value) + } + }) + } + + pub fn provenance( + self, + ) -> impl ExactSizeIterator + FusedIterator + 'a { + self.observation + .provenance(self.index) + .expect("package case originates from the same observation") + .iter() + .copied() + .map(PackageProgramScenarioIdV1::from_core) + } +} + +/// Whether one constraint cell gates selection or is retained for reporting. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PackageProgramConstraintModeV1 { + Hard, + ReportOnly, +} + +/// One selected/fixed case × constraint cell; state is owned by its certificate. +#[derive(Clone, Copy)] +pub struct PackageProgramVerifiedCellV1<'a> { + inner: &'a CoreProgramConstraintCellV1, +} + +impl<'a> PackageProgramVerifiedCellV1<'a> { + const fn from_core(inner: &'a CoreProgramConstraintCellV1) -> Self { + Self { inner } + } + + pub const fn case_index(self) -> usize { + self.inner.case_index() + } + + pub const fn constraint(self) -> PackageProgramConstraintIdV1 { + PackageProgramConstraintIdV1::from_core(self.inner.constraint()) + } + + pub const fn occurrence(self) -> PackageProgramOccurrenceIdV1 { + PackageProgramOccurrenceIdV1::from_core(self.inner.target()) + } + + pub const fn mode(self) -> PackageProgramConstraintModeV1 { + project_constraint_mode(self.inner) + } + + pub fn assessment(self) -> PackageProgramAssessmentV1<'a> { + project_assessment(self.inner) + } +} + +/// One exhaustive candidate-state × case × constraint conflict cell. +#[derive(Clone, Copy)] +pub struct PackageProgramConflictCellV1<'a> { + inner: &'a CoreProgramConstraintCellV1, +} + +impl<'a> PackageProgramConflictCellV1<'a> { + const fn from_core(inner: &'a CoreProgramConstraintCellV1) -> Self { + Self { inner } + } + + pub const fn state_index(self) -> usize { + self.inner.candidate_state_index() + } + + pub const fn case_index(self) -> usize { + self.inner.case_index() + } + + pub const fn constraint(self) -> PackageProgramConstraintIdV1 { + PackageProgramConstraintIdV1::from_core(self.inner.constraint()) + } + + pub const fn occurrence(self) -> PackageProgramOccurrenceIdV1 { + PackageProgramOccurrenceIdV1::from_core(self.inner.target()) + } + + pub const fn mode(self) -> PackageProgramConstraintModeV1 { + project_constraint_mode(self.inner) + } + + pub fn assessment(self) -> PackageProgramAssessmentV1<'a> { + project_assessment(self.inner) + } +} + +const fn project_constraint_mode( + cell: &CoreProgramConstraintCellV1, +) -> PackageProgramConstraintModeV1 { + if cell.is_hard() { + PackageProgramConstraintModeV1::Hard + } else { + PackageProgramConstraintModeV1::ReportOnly + } +} + +fn project_assessment(cell: &CoreProgramConstraintCellV1) -> PackageProgramAssessmentV1<'_> { + match cell.result() { + ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::ExactSrgb8(evidence)) => { + PackageProgramAssessmentV1::ExactSrgb8(PackageProgramExactSrgb8EvidenceV1 { + inner: PackageProgramExactSrgb8EvidenceRefV1::Pass(evidence), + }) + } + ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::ExactSrgb8( + evidence, + )) => PackageProgramAssessmentV1::ExactSrgb8(PackageProgramExactSrgb8EvidenceV1 { + inner: PackageProgramExactSrgb8EvidenceRefV1::Violation(evidence), + }), + ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::Wcag22Srgb8(evidence)) => { + PackageProgramAssessmentV1::Wcag22Srgb8(PackageProgramWcag22Srgb8EvidenceV1 { + inner: PackageProgramWcag22Srgb8EvidenceRefV1::Pass(evidence), + }) + } + ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::Wcag22Srgb8( + evidence, + )) => PackageProgramAssessmentV1::Wcag22Srgb8(PackageProgramWcag22Srgb8EvidenceV1 { + inner: PackageProgramWcag22Srgb8EvidenceRefV1::Violation(evidence), + }), + } +} + +/// Stored evaluator family. Its sealed witness retains the incompatible verdict. +#[derive(Clone, Copy)] +pub enum PackageProgramAssessmentV1<'a> { + ExactSrgb8(PackageProgramExactSrgb8EvidenceV1<'a>), + Wcag22Srgb8(PackageProgramWcag22Srgb8EvidenceV1<'a>), +} + +impl<'a> PackageProgramAssessmentV1<'a> { + pub const fn verdict(self) -> PackageProgramVerdictV1 { + match self { + Self::ExactSrgb8(value) => value.verdict(), + Self::Wcag22Srgb8(value) => value.verdict(), + } + } + + pub fn binding(self) -> PackageProgramPointBindingV1<'a> { + match self { + Self::ExactSrgb8(value) => value.binding(), + Self::Wcag22Srgb8(value) => value.binding(), } } +} + +/// Incompatible stored classifier outcomes. Clients cannot construct evidence. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PackageProgramVerdictV1 { + Pass, + Violation, +} - pub const fn kind(self) -> PackageProgramCertificateKindV1 { +#[derive(Clone, Copy)] +enum PackageProgramExactSrgb8EvidenceRefV1<'a> { + Pass(&'a CoreExactPassEvidenceV1), + Violation(&'a CoreExactViolationEvidenceV1), +} + +/// Exact-sRGB8 expected value plus retained physical composition and modeled +/// tristimulus/context. +#[derive(Clone, Copy)] +pub struct PackageProgramExactSrgb8EvidenceV1<'a> { + inner: PackageProgramExactSrgb8EvidenceRefV1<'a>, +} + +impl<'a> PackageProgramExactSrgb8EvidenceV1<'a> { + pub const fn verdict(self) -> PackageProgramVerdictV1 { match self.inner { - PackageProgramCertificateRefV1::Verified(_) => { - PackageProgramCertificateKindV1::Verified + PackageProgramExactSrgb8EvidenceRefV1::Pass(_) => PackageProgramVerdictV1::Pass, + PackageProgramExactSrgb8EvidenceRefV1::Violation(_) => { + PackageProgramVerdictV1::Violation } - PackageProgramCertificateRefV1::Conflict(_) => { - PackageProgramCertificateKindV1::Conflict + } + } + + pub fn expected(self) -> Srgb8 { + match self.inner { + PackageProgramExactSrgb8EvidenceRefV1::Pass(value) => value.target(), + PackageProgramExactSrgb8EvidenceRefV1::Violation(value) => value.target(), + } + } + + pub fn binding(self) -> PackageProgramPointBindingV1<'a> { + let value = match self.inner { + PackageProgramExactSrgb8EvidenceRefV1::Pass(value) => value.binding(), + PackageProgramExactSrgb8EvidenceRefV1::Violation(value) => value.binding(), + }; + PackageProgramPointBindingV1 { inner: value } + } +} + +#[derive(Clone, Copy)] +enum PackageProgramWcag22Srgb8EvidenceRefV1<'a> { + Pass(&'a CoreWcag22PassEvidenceV1), + Violation(&'a CoreWcag22ViolationEvidenceV1), +} + +/// WCAG 2.2 profile, criterion, luminance evidence, physical composition and +/// modeled tristimulus/context retained by the Core report. +#[derive(Clone, Copy)] +pub struct PackageProgramWcag22Srgb8EvidenceV1<'a> { + inner: PackageProgramWcag22Srgb8EvidenceRefV1<'a>, +} + +impl<'a> PackageProgramWcag22Srgb8EvidenceV1<'a> { + pub const fn verdict(self) -> PackageProgramVerdictV1 { + match self.inner { + PackageProgramWcag22Srgb8EvidenceRefV1::Pass(_) => PackageProgramVerdictV1::Pass, + PackageProgramWcag22Srgb8EvidenceRefV1::Violation(_) => { + PackageProgramVerdictV1::Violation } } } - /// Revision bound into this exact evidence object. - pub const fn revision(self) -> u64 { - let revision = match self.inner { - PackageProgramCertificateRefV1::Verified(value) => { - value.report().observation().revision() + pub fn profile_id(self) -> Wcag22ProfileIdV1 { + match self.inner { + PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { + value.measurement().value().profile_id() } - PackageProgramCertificateRefV1::Conflict(value) => { - value.report().observation().revision() + PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { + value.measurement().value().profile_id() + } + } + } + + pub fn criterion(self) -> Wcag22CriterionV1 { + match self.inner { + PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { + value.measurement().value().criterion() + } + PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { + value.measurement().value().criterion() + } + } + } + + pub fn foreground_luminance(self) -> Wcag22LuminanceBoundsQ55V1 { + let measurement = match self.inner { + PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { + value.measurement().value().measurement() + } + PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { + value.measurement().value().measurement() } }; - revision.value() + measurement.foreground_luminance } - #[cfg(test)] - pub(crate) fn observation_backing_ptr_for_test(self) -> *const () { + pub fn background_luminance(self) -> Wcag22LuminanceBoundsQ55V1 { + let measurement = match self.inner { + PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { + value.measurement().value().measurement() + } + PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { + value.measurement().value().measurement() + } + }; + measurement.background_luminance + } + + pub fn numerical_evidence(self) -> &'a NumericalDecisionEvidenceV1 { match self.inner { - PackageProgramCertificateRefV1::Verified(value) => { - value.report().observation().backing_ptr_for_test() + PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => { + value.measurement().value().evidence() + } + PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => { + value.measurement().value().evidence() + } + } + } + + pub fn binding(self) -> PackageProgramPointBindingV1<'a> { + let value = match self.inner { + PackageProgramWcag22Srgb8EvidenceRefV1::Pass(value) => value.binding(), + PackageProgramWcag22Srgb8EvidenceRefV1::Violation(value) => value.binding(), + }; + PackageProgramPointBindingV1 { inner: value } + } +} + +/// Retained physical composition and modeled tristimulus/context shared by an +/// evaluator witness. +#[derive(Clone, Copy)] +pub struct PackageProgramPointBindingV1<'a> { + inner: &'a ProgramVisiblePointBindingV1, +} + +impl<'a> PackageProgramPointBindingV1<'a> { + pub const fn physical(self) -> PackageProgramPhysicalPointV1<'a> { + match self.inner.physical().occurrence().profile() { + CompositionProfileV1::EncodedSrgb8SourceOverV1 => { + PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver( + PackageProgramEncodedSrgb8SourceOverV1 { inner: self.inner }, + ) } - PackageProgramCertificateRefV1::Conflict(value) => { - value.report().observation().backing_ptr_for_test() + } + } + + pub const fn modeled(self) -> PackageProgramModeledPointV1<'a> { + match self.inner.modeled_lcs().provenance().binding() { + AdmittedSrgb8TristimulusBindingV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1V1 => { + PackageProgramModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1( + PackageProgramModeledTristimulusV1 { inner: self.inner }, + ) } } } } +/// Closed exact physical-composition family. +#[derive(Clone, Copy)] +pub enum PackageProgramPhysicalPointV1<'a> { + EncodedSrgb8SourceOver(PackageProgramEncodedSrgb8SourceOverV1<'a>), +} + +#[derive(Clone, Copy)] +pub struct PackageProgramEncodedSrgb8SourceOverV1<'a> { + inner: &'a ProgramVisiblePointBindingV1, +} + +impl PackageProgramEncodedSrgb8SourceOverV1<'_> { + pub const fn subject_paint(self) -> PackageProgramPaintIdV1 { + PackageProgramPaintIdV1::from_core(self.inner.physical().program_occurrence().subject()) + } + + pub const fn backdrop_surface(self) -> PackageProgramSurfaceIdV1 { + PackageProgramSurfaceIdV1::from_core( + self.inner + .physical() + .program_occurrence() + .backdrop_surface(), + ) + } + + pub const fn subject(self) -> Srgb8 { + Srgb8::new(self.inner.physical().occurrence().subject_rgb()) + } + + pub const fn opacity(self) -> f64 { + f64::from_bits(self.inner.physical().occurrence().subject_opacity_bits()) + } + + pub const fn backdrop(self) -> Srgb8 { + Srgb8::new(self.inner.physical().occurrence().backdrop_rgb()) + } + + pub const fn visible(self) -> Srgb8 { + Srgb8::new(self.inner.physical().occurrence().output_rgb()) + } +} + +/// Closed modeled-tristimulus provenance family. +#[derive(Clone, Copy)] +pub enum PackageProgramModeledPointV1<'a> { + Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(PackageProgramModeledTristimulusV1<'a>), +} + +#[derive(Clone, Copy)] +pub struct PackageProgramModeledTristimulusV1<'a> { + inner: &'a ProgramVisiblePointBindingV1, +} + +impl PackageProgramModeledTristimulusV1<'_> { + pub fn xyz(self) -> [f64; 3] { + self.inner.modeled_lcs().derivation().sample().xyz() + } + + pub const fn appearance_context(self) -> PackageProgramAppearanceContextV1 { + PackageProgramAppearanceContextV1(self.inner.modeled_lcs().occurrence().context()) + } +} + +/// One Core-certified output Paint. No output exists for Conflict. +#[derive(Clone, Copy)] +pub struct PackageProgramCertifiedOutputV1<'a> { + inner: &'a ProgramOutputV1, +} + +impl<'a> PackageProgramCertifiedOutputV1<'a> { + const fn from_core(inner: &'a ProgramOutputV1) -> Self { + Self { inner } + } + + pub const fn output_slot(self) -> PackageProgramOutputSlotIdV1 { + PackageProgramOutputSlotIdV1::from_core((*self.inner).output()) + } + + pub const fn paint(self) -> PackageProgramPaintIdV1 { + PackageProgramPaintIdV1::from_core((*self.inner).paint().id()) + } + + pub const fn source(self) -> Srgb8 { + (*self.inner).paint().source() + } + + pub const fn opacity(self) -> f64 { + (*self.inner).paint().opacity().value() + } +} + +/// A Set operation is structurally tied to the exact Verified certificate. +#[derive(Clone, Copy)] +pub struct PackageProgramSetV1<'a> { + output: &'a ProgramOutputV1, + certificate: PackageProgramVerifiedCertificateV1<'a>, +} + +impl<'a> PackageProgramSetV1<'a> { + pub const fn output_slot(self) -> PackageProgramOutputSlotIdV1 { + PackageProgramOutputSlotIdV1::from_core((*self.output).output()) + } + + pub const fn source(self) -> Srgb8 { + (*self.output).paint().source() + } + + pub const fn opacity(self) -> f64 { + (*self.output).paint().opacity().value() + } + + pub const fn certificate(self) -> PackageProgramVerifiedCertificateV1<'a> { + self.certificate + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PackageProgramRemoveV1 { + output_slot: PackageProgramOutputSlotIdV1, +} + +impl PackageProgramRemoveV1 { + pub const fn output_slot(self) -> PackageProgramOutputSlotIdV1 { + self.output_slot + } +} + +/// A Hold operation is structurally tied to the retained Verified certificate. +#[derive(Clone, Copy)] +pub struct PackageProgramHoldV1<'a> { + output_slot: PackageProgramOutputSlotIdV1, + certificate: PackageProgramVerifiedCertificateV1<'a>, +} + +impl<'a> PackageProgramHoldV1<'a> { + pub const fn output_slot(self) -> PackageProgramOutputSlotIdV1 { + self.output_slot + } + + pub const fn certificate(self) -> PackageProgramVerifiedCertificateV1<'a> { + self.certificate + } +} + /// Closed total operation union over opaque output slots. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum PackageProgramOperationV1 { - Set { - output_slot: PackageProgramOutputSlotIdV1, - source: Srgb8, - opacity: f64, - certificate_index: usize, - }, - Remove { - output_slot: PackageProgramOutputSlotIdV1, - }, - Hold { - output_slot: PackageProgramOutputSlotIdV1, - certificate_index: usize, - }, +#[derive(Clone, Copy)] +pub enum PackageProgramOperationV1<'a> { + Set(PackageProgramSetV1<'a>), + Remove(PackageProgramRemoveV1), + Hold(PackageProgramHoldV1<'a>), } struct PackageProgramCertificatesV1<'a> { @@ -1346,10 +1979,13 @@ impl FusedIterator for PackageProgramCertificatesV1<'_> {} enum PackageProgramOperationSourceV1<'a> { Empty, - Set(slice::Iter<'a, ProgramOutputV1>), + Set { + outputs: slice::Iter<'a, ProgramOutputV1>, + certificate: PackageProgramVerifiedCertificateV1<'a>, + }, Hold { slots: slice::Iter<'a, PackageProgramOutputSlotIdV1>, - certificate_index: usize, + certificate: PackageProgramVerifiedCertificateV1<'a>, }, Remove(slice::Iter<'a, PackageProgramOutputSlotIdV1>), } @@ -1358,33 +1994,32 @@ struct PackageProgramOperationsV1<'a> { inner: PackageProgramOperationSourceV1<'a>, } -impl Iterator for PackageProgramOperationsV1<'_> { - type Item = PackageProgramOperationV1; +impl<'a> Iterator for PackageProgramOperationsV1<'a> { + type Item = PackageProgramOperationV1<'a>; fn next(&mut self) -> Option { match &mut self.inner { PackageProgramOperationSourceV1::Empty => None, - PackageProgramOperationSourceV1::Set(outputs) => { - let output = *outputs.next()?; - let paint = output.paint(); - Some(PackageProgramOperationV1::Set { - output_slot: PackageProgramOutputSlotIdV1::from_core(output.output()), - source: paint.source(), - opacity: paint.opacity().value(), - certificate_index: 0, - }) - } - PackageProgramOperationSourceV1::Hold { - slots, - certificate_index, - } => Some(PackageProgramOperationV1::Hold { - output_slot: *slots.next()?, - certificate_index: *certificate_index, - }), + PackageProgramOperationSourceV1::Set { + outputs, + certificate, + } => { + let output = outputs.next()?; + Some(PackageProgramOperationV1::Set(PackageProgramSetV1 { + output, + certificate: *certificate, + })) + } + PackageProgramOperationSourceV1::Hold { slots, certificate } => { + Some(PackageProgramOperationV1::Hold(PackageProgramHoldV1 { + output_slot: *slots.next()?, + certificate: *certificate, + })) + } PackageProgramOperationSourceV1::Remove(slots) => { - Some(PackageProgramOperationV1::Remove { + Some(PackageProgramOperationV1::Remove(PackageProgramRemoveV1 { output_slot: *slots.next()?, - }) + })) } } } @@ -1392,7 +2027,7 @@ impl Iterator for PackageProgramOperationsV1<'_> { fn size_hint(&self) -> (usize, Option) { let remaining = match &self.inner { PackageProgramOperationSourceV1::Empty => 0, - PackageProgramOperationSourceV1::Set(outputs) => outputs.len(), + PackageProgramOperationSourceV1::Set { outputs, .. } => outputs.len(), PackageProgramOperationSourceV1::Hold { slots, .. } | PackageProgramOperationSourceV1::Remove(slots) => slots.len(), }; diff --git a/crates/labcolors-core/src/program_mixed_evaluator_tests.rs b/crates/labcolors-core/src/program_mixed_evaluator_tests.rs index 7b30e193..7a3a91a7 100644 --- a/crates/labcolors-core/src/program_mixed_evaluator_tests.rs +++ b/crates/labcolors-core/src/program_mixed_evaluator_tests.rs @@ -1,28 +1,36 @@ +use core::iter::FusedIterator; + use crate::Srgb8; -use crate::appearance::{OccurrenceId, PaintId, SurfaceId, SurfaceInputPortId}; +use crate::appearance::{OccurrenceId, OpacityInputId, PaintId, SurfaceId, SurfaceInputPortId}; use crate::constraints::{ ExactConstraintIdentityV1, ExactIdentityCapabilityV1, ExactIdentityReleaseV1, + ProgramVisiblePointBindingV1, }; use crate::lcs_occurrence::{ AdaptingLuminanceCdM2, AppearanceContextId, AppearanceContextSchemaReleaseId, - BackgroundLuminanceRatio, ColorSignal, IEC_SRGB_D65_XYZ_FRAME_V1, SurroundProfileId, + BackgroundLuminanceRatio, ColorSignal, IEC_SRGB_D65_XYZ_FRAME_V1, + MODELED_TRISTIMULUS_DERIVATION_CALLS, SurroundProfileId, }; use crate::observation::{ ObservationGroupId, ObservationPayloadInput, ObservationStreamId, ObservationUpdateInput, ObservedScenarioSetInput, Revision, ScenarioId, ScenarioInput, SurfaceInputBinding, }; use crate::package_bridge::{ - PackageProgramCertificateKindV1, PackageProgramOperationV1, PackageProgramOutputSlotIdV1, - PackageProgramOwnerV1, PackageProgramScenarioV1, PackageProgramStateKindV1, - PackageProgramUpdateErrorKindV1, PackageProgramUpdateV1, + PackageProgramAssessmentV1, PackageProgramCertificateV1, PackageProgramConflictCellV1, + PackageProgramModeledPointV1, PackageProgramObservationV1, PackageProgramOperationV1, + PackageProgramOutputSlotIdV1, PackageProgramOwnerV1, PackageProgramPhysicalPointV1, + PackageProgramScenarioV1, PackageProgramSignalV1, PackageProgramStateKindV1, + PackageProgramStateViewV1, PackageProgramSurroundV1, PackageProgramUpdateErrorKindV1, + PackageProgramUpdateV1, PackageProgramVerdictV1, PackageProgramVerifiedCellV1, }; use crate::program_session::{ - CompiledCoreProgramV1, CompositionProfile, ConstraintId, ConstraintInvocation, ConstraintSet, - CoreProgramConstraintInvocationV1, CoreProgramEvaluatorsV1, CoreProgramPassEvidenceV1, - CoreProgramV1, CoreProgramViolationEvidenceV1, DeclaredJointSelectionV1, JointCandidateStateV1, - ObservationGroup, Occurrence, OutputBinding, OutputSlotId, Paint, Program, - ProgramConstraintResultV1, Source, SourceId, Surface, Target, TargetCandidateChoiceV1, - TargetCandidateId, TargetCandidateV1, TargetId, + CORE_PROGRAM_ASSESSMENT_CALLS, CompiledCoreProgramV1, CompositionProfile, ConstraintId, + ConstraintInvocation, ConstraintSet, CoreProgramConstraintInvocationV1, + CoreProgramEvaluatorsV1, CoreProgramPassEvidenceV1, CoreProgramV1, + CoreProgramViolationEvidenceV1, DeclaredJointSelectionV1, JointCandidateStateV1, + ObservationGroup, Occurrence, OpacityInput, OutputBinding, OutputSlotId, Paint, Program, + ProgramConstraintCellV1, ProgramConstraintResultV1, Source, SourceId, Surface, Target, + TargetCandidateChoiceV1, TargetCandidateId, TargetCandidateV1, TargetId, }; use crate::session::SessionState; use crate::wcag22::{Wcag22CriterionV1, wcag22_profile_v1}; @@ -137,6 +145,523 @@ fn finite_program(candidate_signals: [[u8; 3]; 2]) -> CompiledCoreProgramV1 { .unwrap() } +fn fixed_translucent_program() -> CompiledCoreProgramV1 { + const OPACITY: OpacityInputId = OpacityInputId::new(12); + const TRANSLUCENT_PAINT: PaintId = PaintId::new(13); + Program::new( + vec![Source::new(SOURCE, signal([0; 3]))], + vec![Target::fixed(TARGET, SOURCE)], + ObservationGroup::new(GROUP, vec![SURFACE_PORT]), + vec![OpacityInput::new(OPACITY, 0.5)], + vec![ + Paint::Solid { + id: PAINT, + target: TARGET, + }, + Paint::Opacity { + id: TRANSLUCENT_PAINT, + source: PAINT, + opacity: OPACITY, + }, + ], + vec![Surface::Input { + id: SURFACE, + input: SURFACE_PORT, + }], + vec![Occurrence::new( + OCCURRENCE, + TRANSLUCENT_PAINT, + SURFACE, + CompositionProfile::EncodedSrgb8SourceOverV1, + context(), + )], + ConstraintSet::new( + vec![ConstraintInvocation::hard( + EXACT_CONSTRAINT, + OCCURRENCE, + CoreProgramConstraintInvocationV1::ExactSrgb8(Srgb8::new([0x80; 3])), + )], + vec![], + ), + vec![OutputBinding::new(OUTPUT, TRANSLUCENT_PAINT)], + CoreProgramEvaluatorsV1, + ) + .compile() + .unwrap() +} + +fn assert_package_observation_matches_core( + package: PackageProgramObservationV1<'_>, + core: &crate::observation::RevisionBoundObservationV1, +) { + assert_eq!(package.stream().value(), core.stream().value()); + assert_eq!(package.revision(), core.revision().value()); + assert_eq!( + package + .surface_input_ports() + .map(|port| port.value()) + .collect::>(), + core.schema() + .iter() + .map(|port| port.value()) + .collect::>(), + ); + + let package_cases = package + .physical_cases() + .map(|case| { + let values = case + .values() + .map(|value| match value { + PackageProgramSignalV1::Iec61966Srgb8D65(value) => value, + }) + .collect::>(); + let provenance = case + .provenance() + .map(|scenario| scenario.value()) + .collect::>(); + (values, provenance) + }) + .collect::>(); + let core_cases = (0..core.physical_case_count()) + .map(|case_index| { + let values = core + .physical_values(case_index) + .unwrap() + .iter() + .map(|signal| signal.srgb8()) + .collect::>(); + let provenance = core + .provenance(case_index) + .unwrap() + .iter() + .map(|scenario| scenario.value()) + .collect::>(); + (values, provenance) + }) + .collect::>(); + assert_eq!(package_cases, core_cases); +} + +fn assert_package_binding_matches_core( + package: PackageProgramAssessmentV1<'_>, + core: &ProgramVisiblePointBindingV1, + expected_occurrence: OccurrenceId, +) -> (Srgb8, Srgb8) { + let core_physical = core.physical(); + let core_occurrence = core_physical.occurrence(); + let core_program_occurrence = core_physical.program_occurrence(); + assert_eq!(core_program_occurrence.occurrence(), expected_occurrence); + let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(package_physical) = + package.binding().physical(); + assert_eq!( + package_physical.subject_paint().value(), + core_program_occurrence.subject().value() + ); + assert_eq!( + package_physical.backdrop_surface().value(), + core_program_occurrence.backdrop_surface().value() + ); + assert_eq!( + package_physical.subject(), + Srgb8::new(core_occurrence.subject_rgb()) + ); + assert_eq!( + package_physical.opacity().to_bits(), + core_occurrence.subject_opacity_bits() + ); + assert_eq!( + package_physical.backdrop(), + Srgb8::new(core_occurrence.backdrop_rgb()) + ); + assert_eq!( + package_physical.visible(), + Srgb8::new(core_occurrence.output_rgb()) + ); + + let PackageProgramModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1( + package_modeled, + ) = package.binding().modeled(); + assert_eq!( + package_modeled.xyz().map(f64::to_bits), + core.modeled_lcs() + .derivation() + .sample() + .xyz() + .map(f64::to_bits) + ); + let package_context = package_modeled.appearance_context(); + let core_context = core.modeled_lcs().occurrence().context(); + assert_eq!( + package_context.adapting_luminance_cd_m2().to_bits(), + core_context.adapting_luminance_cd_m2().to_bits() + ); + assert_eq!( + package_context.background_luminance_ratio_yb_yw().to_bits(), + core_context.background_luminance_ratio().to_bits() + ); + let core_surround = match core_context.surround_profile() { + SurroundProfileId::AverageV1 => PackageProgramSurroundV1::Average, + SurroundProfileId::DimV1 => PackageProgramSurroundV1::Dim, + SurroundProfileId::DarkV1 => PackageProgramSurroundV1::Dark, + }; + assert_eq!(package_context.surround(), core_surround); + + (package_physical.visible(), package_physical.backdrop()) +} + +fn assert_package_assessment_matches_core( + package: PackageProgramAssessmentV1<'_>, + core: &ProgramConstraintResultV1, + expected_occurrence: OccurrenceId, +) { + match (package, core) { + ( + PackageProgramAssessmentV1::ExactSrgb8(package), + ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::ExactSrgb8(core)), + ) => { + assert_eq!(package.verdict(), PackageProgramVerdictV1::Pass); + assert_eq!(package.expected(), core.target()); + let (visible, _) = assert_package_binding_matches_core( + PackageProgramAssessmentV1::ExactSrgb8(package), + core.binding(), + expected_occurrence, + ); + assert_eq!(visible, core.actual()); + } + ( + PackageProgramAssessmentV1::ExactSrgb8(package), + ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::ExactSrgb8(core)), + ) => { + assert_eq!(package.verdict(), PackageProgramVerdictV1::Violation); + assert_eq!(package.expected(), core.target()); + let (visible, _) = assert_package_binding_matches_core( + PackageProgramAssessmentV1::ExactSrgb8(package), + core.binding(), + expected_occurrence, + ); + assert_eq!(visible, core.actual()); + } + ( + PackageProgramAssessmentV1::Wcag22Srgb8(package), + ProgramConstraintResultV1::Pass(CoreProgramPassEvidenceV1::Wcag22Srgb8(core)), + ) => { + assert_eq!(package.verdict(), PackageProgramVerdictV1::Pass); + let measurement = core.measurement().value(); + assert_eq!(package.profile_id(), measurement.profile_id()); + assert_eq!(package.criterion(), measurement.criterion()); + assert_eq!( + package.foreground_luminance(), + measurement.measurement().foreground_luminance + ); + assert_eq!( + package.background_luminance(), + measurement.measurement().background_luminance + ); + assert_eq!(package.numerical_evidence(), measurement.evidence()); + let (visible, backdrop) = assert_package_binding_matches_core( + PackageProgramAssessmentV1::Wcag22Srgb8(package), + core.binding(), + expected_occurrence, + ); + assert_eq!(visible, Srgb8::new(measurement.measurement().foreground)); + assert_eq!(backdrop, Srgb8::new(measurement.measurement().background)); + } + ( + PackageProgramAssessmentV1::Wcag22Srgb8(package), + ProgramConstraintResultV1::Violation(CoreProgramViolationEvidenceV1::Wcag22Srgb8(core)), + ) => { + assert_eq!(package.verdict(), PackageProgramVerdictV1::Violation); + let measurement = core.measurement().value(); + assert_eq!(package.profile_id(), measurement.profile_id()); + assert_eq!(package.criterion(), measurement.criterion()); + assert_eq!( + package.foreground_luminance(), + measurement.measurement().foreground_luminance + ); + assert_eq!( + package.background_luminance(), + measurement.measurement().background_luminance + ); + assert_eq!(package.numerical_evidence(), measurement.evidence()); + let (visible, backdrop) = assert_package_binding_matches_core( + PackageProgramAssessmentV1::Wcag22Srgb8(package), + core.binding(), + expected_occurrence, + ); + assert_eq!(visible, Srgb8::new(measurement.measurement().foreground)); + assert_eq!(backdrop, Srgb8::new(measurement.measurement().background)); + } + _ => panic!("package assessment family or verdict drifted from Core"), + } +} + +fn assert_verified_cell_matches_core( + package: PackageProgramVerifiedCellV1<'_>, + core: &ProgramConstraintCellV1, + selected_state_index: usize, +) { + assert_eq!(core.candidate_state_index(), selected_state_index); + assert_eq!(package.case_index(), core.case_index()); + assert_eq!(package.constraint().value(), core.constraint().value()); + assert_eq!(package.occurrence().value(), core.target().value()); + assert_eq!( + matches!( + package.mode(), + crate::package_bridge::PackageProgramConstraintModeV1::Hard + ), + core.is_hard() + ); + assert_package_assessment_matches_core(package.assessment(), core.result(), core.target()); +} + +fn assert_conflict_cell_matches_core( + package: PackageProgramConflictCellV1<'_>, + core: &ProgramConstraintCellV1, +) { + assert_eq!(package.state_index(), core.candidate_state_index()); + assert_eq!(package.case_index(), core.case_index()); + assert_eq!(package.constraint().value(), core.constraint().value()); + assert_eq!(package.occurrence().value(), core.target().value()); + assert_eq!( + matches!( + package.mode(), + crate::package_bridge::PackageProgramConstraintModeV1::Hard + ), + core.is_hard() + ); + assert_package_assessment_matches_core(package.assessment(), core.result(), core.target()); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct ProjectionProbe { + iterators: usize, + certificates: usize, + cases: usize, + values: usize, + provenance: usize, + cells: usize, + outputs: usize, + operations: usize, + exact_assessments: usize, + wcag_assessments: usize, + iterator_laws_hold: bool, + checksum: u64, +} + +impl ProjectionProbe { + const fn new() -> Self { + Self { + iterators: 0, + certificates: 0, + cases: 0, + values: 0, + provenance: 0, + cells: 0, + outputs: 0, + operations: 0, + exact_assessments: 0, + wcag_assessments: 0, + iterator_laws_hold: true, + checksum: 0, + } + } + + fn mix(&mut self, value: u64) { + self.checksum = self.checksum.rotate_left(1) ^ value; + } + + fn mix_bytes(&mut self, bytes: &[u8]) { + for byte in bytes { + self.mix(u64::from(*byte)); + } + } + + fn mix_srgb8(&mut self, value: Srgb8) { + self.mix_bytes(&value.bytes()); + } +} + +fn consume_exact_fused( + mut iterator: I, + probe: &mut ProjectionProbe, + mut consume: impl FnMut(I::Item, &mut ProjectionProbe), +) where + I: ExactSizeIterator + FusedIterator, +{ + probe.iterators += 1; + let mut remaining = iterator.len(); + let initial_len = remaining; + probe.iterator_laws_hold &= iterator.size_hint() == (remaining, Some(remaining)); + for _ in 0..initial_len { + let Some(value) = iterator.next() else { + probe.iterator_laws_hold = false; + break; + }; + remaining -= 1; + probe.iterator_laws_hold &= iterator.len() == remaining; + probe.iterator_laws_hold &= iterator.size_hint() == (remaining, Some(remaining)); + consume(value, probe); + } + probe.iterator_laws_hold &= remaining == 0; + probe.iterator_laws_hold &= iterator.len() == 0; + probe.iterator_laws_hold &= iterator.next().is_none(); + probe.iterator_laws_hold &= iterator.next().is_none(); +} + +fn consume_package_assessment( + assessment: PackageProgramAssessmentV1<'_>, + probe: &mut ProjectionProbe, +) { + probe.mix(match assessment.verdict() { + PackageProgramVerdictV1::Pass => 1, + PackageProgramVerdictV1::Violation => 2, + }); + match assessment { + PackageProgramAssessmentV1::ExactSrgb8(evidence) => { + probe.exact_assessments += 1; + probe.mix_srgb8(evidence.expected()); + } + PackageProgramAssessmentV1::Wcag22Srgb8(evidence) => { + probe.wcag_assessments += 1; + probe.mix_bytes(evidence.profile_id().key().as_bytes()); + probe.mix_bytes(evidence.criterion().key().as_bytes()); + probe.mix(evidence.foreground_luminance().lower()); + probe.mix(evidence.foreground_luminance().upper()); + probe.mix(evidence.background_luminance().lower()); + probe.mix(evidence.background_luminance().upper()); + probe.mix_bytes(evidence.numerical_evidence().class_key().as_bytes()); + } + } + + let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(physical) = + assessment.binding().physical(); + probe.mix(u64::from(physical.subject_paint().value())); + probe.mix(u64::from(physical.backdrop_surface().value())); + probe.mix_srgb8(physical.subject()); + probe.mix(physical.opacity().to_bits()); + probe.mix_srgb8(physical.backdrop()); + probe.mix_srgb8(physical.visible()); + + let PackageProgramModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1(modeled) = + assessment.binding().modeled(); + for coordinate in modeled.xyz() { + probe.mix(coordinate.to_bits()); + } + let context = modeled.appearance_context(); + probe.mix(context.adapting_luminance_cd_m2().to_bits()); + probe.mix(context.background_luminance_ratio_yb_yw().to_bits()); + probe.mix(match context.surround() { + PackageProgramSurroundV1::Average => 1, + PackageProgramSurroundV1::Dim => 2, + PackageProgramSurroundV1::Dark => 3, + }); +} + +fn consume_package_projection(view: PackageProgramStateViewV1<'_>) -> ProjectionProbe { + let mut probe = ProjectionProbe::new(); + probe.mix(match view.kind() { + PackageProgramStateKindV1::Waiting => 1, + PackageProgramStateKindV1::Ready => 2, + PackageProgramStateKindV1::Failed => 3, + PackageProgramStateKindV1::Stale => 4, + }); + probe.mix(view.revision().unwrap_or_default()); + probe.mix( + view.cause_certificate_index() + .map_or(0, |index| index as u64 + 1), + ); + + consume_exact_fused(view.certificates(), &mut probe, |certificate, probe| { + probe.certificates += 1; + probe.mix_bytes(certificate.content_identity().as_bytes()); + let observation = certificate.observation(); + probe.mix(u64::from(observation.stream().value())); + probe.mix(observation.revision()); + consume_exact_fused(observation.surface_input_ports(), probe, |port, probe| { + probe.mix(u64::from(port.value())); + }); + consume_exact_fused(observation.physical_cases(), probe, |case, probe| { + probe.cases += 1; + consume_exact_fused(case.values(), probe, |value, probe| { + probe.values += 1; + let PackageProgramSignalV1::Iec61966Srgb8D65(value) = value; + probe.mix_srgb8(value); + }); + consume_exact_fused(case.provenance(), probe, |scenario, probe| { + probe.provenance += 1; + probe.mix(u64::from(scenario.value())); + }); + }); + match certificate { + PackageProgramCertificateV1::Verified(verified) => { + probe.mix( + verified + .selected_state_index() + .map_or(0, |index| index as u64 + 1), + ); + consume_exact_fused(verified.cells(), probe, |cell, probe| { + probe.cells += 1; + probe.mix(cell.case_index() as u64); + probe.mix(u64::from(cell.constraint().value())); + probe.mix(u64::from(cell.occurrence().value())); + probe.mix(match cell.mode() { + crate::package_bridge::PackageProgramConstraintModeV1::Hard => 1, + crate::package_bridge::PackageProgramConstraintModeV1::ReportOnly => 2, + }); + consume_package_assessment(cell.assessment(), probe); + }); + consume_exact_fused(verified.outputs(), probe, |output, probe| { + probe.outputs += 1; + probe.mix(u64::from(output.output_slot().value())); + probe.mix(u64::from(output.paint().value())); + probe.mix_srgb8(output.source()); + probe.mix(output.opacity().to_bits()); + }); + } + PackageProgramCertificateV1::Conflict(conflict) => { + probe.mix(conflict.considered_state_count() as u64); + consume_exact_fused(conflict.cells(), probe, |cell, probe| { + probe.cells += 1; + probe.mix(cell.state_index() as u64); + probe.mix(cell.case_index() as u64); + probe.mix(u64::from(cell.constraint().value())); + probe.mix(u64::from(cell.occurrence().value())); + probe.mix(match cell.mode() { + crate::package_bridge::PackageProgramConstraintModeV1::Hard => 1, + crate::package_bridge::PackageProgramConstraintModeV1::ReportOnly => 2, + }); + consume_package_assessment(cell.assessment(), probe); + }); + } + } + }); + consume_exact_fused(view.operations(), &mut probe, |operation, probe| { + probe.operations += 1; + match operation { + PackageProgramOperationV1::Set(set) => { + probe.mix(1); + probe.mix(u64::from(set.output_slot().value())); + probe.mix_srgb8(set.source()); + probe.mix(set.opacity().to_bits()); + probe.mix_bytes(set.certificate().content_identity().as_bytes()); + probe.mix(set.certificate().observation().revision()); + } + PackageProgramOperationV1::Remove(remove) => { + probe.mix(2); + probe.mix(u64::from(remove.output_slot().value())); + } + PackageProgramOperationV1::Hold(hold) => { + probe.mix(3); + probe.mix(u64::from(hold.output_slot().value())); + probe.mix_bytes(hold.certificate().content_identity().as_bytes()); + probe.mix(hold.certificate().observation().revision()); + } + } + }); + std::hint::black_box(probe) +} + #[test] fn one_program_retains_typed_exact_and_wcag22_outcomes() { let program: CoreProgramV1 = Program::new( @@ -224,6 +749,42 @@ fn one_program_retains_typed_exact_and_wcag22_outcomes() { ); } +#[test] +fn fixed_package_certificate_retains_none_selection_and_nonunit_output_opacity() { + let owner = PackageProgramOwnerV1::from_compiled(fixed_translucent_program()); + let mut session = owner.instantiate(STREAM.value()).unwrap(); + let white = [Srgb8::new([0xFF; 3])]; + let scenarios = [PackageProgramScenarioV1::new(1, &white)]; + let state = session + .update(PackageProgramUpdateV1::Observed { + revision: 1, + scenarios: &scenarios, + }) + .unwrap(); + let Some(PackageProgramCertificateV1::Verified(certificate)) = state.certificates().next() + else { + panic!("the exact translucent midpoint must be verified"); + }; + assert_eq!(certificate.selected_state_index(), None); + let PackageProgramAssessmentV1::ExactSrgb8(assessment) = + certificate.cells().next().unwrap().assessment() + else { + panic!("the fixed Program has one Exact certificate cell"); + }; + let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(physical) = + assessment.binding().physical(); + assert_eq!(physical.opacity().to_bits(), 0.5_f64.to_bits()); + assert_eq!(physical.visible(), Srgb8::new([0x80; 3])); + assert_eq!( + certificate.outputs().next().unwrap().opacity().to_bits(), + physical.opacity().to_bits() + ); + let Some(PackageProgramOperationV1::Set(set)) = state.operations().next() else { + panic!("Verified must emit one Set"); + }; + assert_eq!(set.opacity().to_bits(), physical.opacity().to_bits()); +} + #[test] fn mixed_families_select_only_a_state_that_passes_every_case_then_recheck_it() { let compiled = finite_program([[0x80; 3], [0; 3]]); @@ -302,6 +863,438 @@ fn mixed_family_conflict_is_exhaustive_and_keeps_report_only_non_gating() { )); } +#[test] +fn package_projection_preserves_every_exposed_ready_and_conflict_field_against_core() { + let ready_core_owner = finite_program([[0x80; 3], [0; 3]]); + let ready_identity = ready_core_owner.content_identity(); + let ready_package_owner = + PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); + let mut ready_core_session = ready_core_owner.instantiate(STREAM).unwrap(); + let mut ready_package_session = ready_package_owner.instantiate(STREAM.value()).unwrap(); + let ready_backdrops = [[0xFF; 3], [0xFF; 3], [0x80; 3]]; + let SessionState::Ready { + current: core_verified, + } = ready_core_session + .update(observed_backdrops(&ready_backdrops)) + .unwrap() + else { + panic!("black is the first state that passes both canonical physical cases"); + }; + let ready_white = [Srgb8::new([0xFF; 3])]; + let ready_gray = [Srgb8::new([0x80; 3])]; + let ready_scenarios = [ + PackageProgramScenarioV1::new(1, &ready_white), + PackageProgramScenarioV1::new(2, &ready_white), + PackageProgramScenarioV1::new(3, &ready_gray), + ]; + let package_ready = ready_package_session + .update(PackageProgramUpdateV1::Observed { + revision: 1, + scenarios: &ready_scenarios, + }) + .unwrap(); + let mut package_certificates = package_ready.certificates(); + assert_eq!(package_certificates.len(), 1); + let Some(PackageProgramCertificateV1::Verified(package_verified)) = package_certificates.next() + else { + panic!("Ready must retain exactly one Verified certificate"); + }; + assert!(package_certificates.next().is_none()); + assert!(package_certificates.next().is_none()); + assert_eq!( + package_verified.content_identity().as_bytes(), + ready_identity.as_bytes() + ); + assert_package_observation_matches_core( + package_verified.observation(), + core_verified.report().observation(), + ); + assert_eq!( + package_verified.selected_state_index(), + core_verified.selected_state_index() + ); + let selected_state_index = core_verified.selected_state_index().unwrap(); + let mut package_cells = package_verified.cells(); + let mut core_cells = core_verified.report().cells().iter(); + assert_eq!(package_cells.len(), core_cells.len()); + while let (Some(package), Some(core)) = (package_cells.next(), core_cells.next()) { + assert_verified_cell_matches_core(package, core, selected_state_index); + assert_eq!(package_cells.len(), core_cells.len()); + } + assert!(package_cells.next().is_none()); + assert!(package_cells.next().is_none()); + assert!(core_cells.next().is_none()); + + let mut package_outputs = package_verified.outputs(); + let mut core_outputs = core_verified.outputs().iter(); + assert_eq!(package_outputs.len(), core_outputs.len()); + while let (Some(package), Some(core)) = (package_outputs.next(), core_outputs.next()) { + assert_eq!(package.output_slot().value(), core.output().value()); + assert_eq!(package.paint().value(), core.paint().id().value()); + assert_eq!(package.source(), core.paint().source()); + assert_eq!( + package.opacity().to_bits(), + core.paint().opacity().value().to_bits() + ); + assert_eq!(package_outputs.len(), core_outputs.len()); + } + assert!(package_outputs.next().is_none()); + assert!(package_outputs.next().is_none()); + assert!(core_outputs.next().is_none()); + let mut ready_operations = package_ready.operations(); + assert_eq!(ready_operations.len(), core_verified.outputs().len()); + for core_output in core_verified.outputs() { + let Some(PackageProgramOperationV1::Set(set)) = ready_operations.next() else { + panic!("every certified output must become exactly one Set"); + }; + assert_eq!(set.output_slot().value(), core_output.output().value()); + assert_eq!(set.source(), core_output.paint().source()); + assert_eq!( + set.opacity().to_bits(), + core_output.paint().opacity().value().to_bits() + ); + assert_eq!( + set.certificate().content_identity(), + package_verified.content_identity() + ); + assert_eq!( + set.certificate().observation().revision(), + package_verified.observation().revision() + ); + } + assert!(ready_operations.next().is_none()); + assert!(ready_operations.next().is_none()); + + let conflict_core_owner = finite_program([[0; 3], [0xFF; 3]]); + let conflict_identity = conflict_core_owner.content_identity(); + let conflict_package_owner = + PackageProgramOwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); + let mut conflict_core_session = conflict_core_owner.instantiate(STREAM).unwrap(); + let mut conflict_package_session = conflict_package_owner.instantiate(STREAM.value()).unwrap(); + let conflict_backdrops = [[0xFF; 3], [0; 3]]; + let SessionState::Failed { + cause: core_conflict, + previous: None, + } = conflict_core_session + .update(observed_backdrops(&conflict_backdrops)) + .unwrap() + else { + panic!("neither black nor white passes both opposing physical cases"); + }; + let conflict_white = [Srgb8::new([0xFF; 3])]; + let conflict_black = [Srgb8::new([0; 3])]; + let conflict_scenarios = [ + PackageProgramScenarioV1::new(1, &conflict_white), + PackageProgramScenarioV1::new(2, &conflict_black), + ]; + let package_failed = conflict_package_session + .update(PackageProgramUpdateV1::Observed { + revision: 1, + scenarios: &conflict_scenarios, + }) + .unwrap(); + let mut package_certificates = package_failed.certificates(); + assert_eq!(package_certificates.len(), 1); + let Some(PackageProgramCertificateV1::Conflict(package_conflict)) = package_certificates.next() + else { + panic!("Failed without previous state must retain one Conflict certificate"); + }; + assert!(package_certificates.next().is_none()); + assert!(package_certificates.next().is_none()); + assert_eq!( + package_conflict.content_identity().as_bytes(), + conflict_identity.as_bytes() + ); + assert_package_observation_matches_core( + package_conflict.observation(), + core_conflict.report().observation(), + ); + assert_eq!( + package_conflict.considered_state_count(), + core_conflict.considered_state_count() + ); + let core_passes = core_conflict + .report() + .cells() + .iter() + .filter(|cell| !cell.result().is_violation()) + .count(); + assert!(core_passes > 0); + assert!(core_passes < core_conflict.report().cells().len()); + let mut package_cells = package_conflict.cells(); + let mut core_cells = core_conflict.report().cells().iter(); + assert_eq!(package_cells.len(), core_cells.len()); + while let (Some(package), Some(core)) = (package_cells.next(), core_cells.next()) { + assert_conflict_cell_matches_core(package, core); + assert_eq!(package_cells.len(), core_cells.len()); + } + assert!(package_cells.next().is_none()); + assert!(package_cells.next().is_none()); + assert!(core_cells.next().is_none()); + let mut failed_operations = package_failed.operations(); + assert_eq!(failed_operations.len(), 1); + assert!(matches!( + failed_operations.next(), + Some(PackageProgramOperationV1::Remove(_)) + )); + assert!(failed_operations.next().is_none()); + assert!(failed_operations.next().is_none()); +} + +#[test] +fn committed_projection_is_zero_alloc_and_repeats_no_composite_transform_or_evaluator_dispatch() { + crate::composition::reset_source_over_evaluation_count(); + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(0)); + CORE_PROGRAM_ASSESSMENT_CALLS.with(|calls| calls.set(0)); + + let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); + let mut session = owner.instantiate(STREAM.value()).unwrap(); + let white = [Srgb8::new([0xFF; 3])]; + let white_only = [PackageProgramScenarioV1::new(1, &white)]; + session + .update(PackageProgramUpdateV1::Observed { + revision: 1, + scenarios: &white_only, + }) + .unwrap(); + let Some(PackageProgramCertificateV1::Verified(ready_certificate)) = + session.state().certificates().next() + else { + panic!("black must be selected for the white-only physical support"); + }; + assert_eq!(ready_certificate.selected_state_index(), Some(0)); + + let ready_compositions = crate::composition::source_over_evaluation_count(); + let ready_derivations = MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get); + let ready_assessments = CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get); + assert!(ready_compositions > 0); + assert!(ready_derivations > 0); + assert!(ready_assessments > 0); + let (ready_probe, ready_allocations) = crate::test_support::measured_allocations(|| { + consume_package_projection(std::hint::black_box(session.state())) + }); + assert_eq!(ready_allocations, 0); + assert!(ready_probe.iterator_laws_hold); + assert_eq!(ready_probe.certificates, 1); + assert_eq!(ready_probe.cases, 1); + assert_eq!(ready_probe.values, 1); + assert_eq!(ready_probe.provenance, 1); + assert_eq!(ready_probe.cells, 2); + assert_eq!(ready_probe.outputs, 1); + assert_eq!(ready_probe.operations, 1); + assert_eq!(ready_probe.exact_assessments, 1); + assert_eq!(ready_probe.wcag_assessments, 1); + assert_ne!(ready_probe.checksum, 0); + assert_eq!( + crate::composition::source_over_evaluation_count(), + ready_compositions + ); + assert_eq!( + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get), + ready_derivations + ); + assert_eq!( + CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get), + ready_assessments + ); + + session + .update(PackageProgramUpdateV1::Unknown { + revision: 2, + reason_id: 7, + }) + .unwrap(); + let stale_compositions = crate::composition::source_over_evaluation_count(); + let stale_derivations = MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get); + let stale_assessments = CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get); + let (stale_probe, stale_allocations) = crate::test_support::measured_allocations(|| { + consume_package_projection(std::hint::black_box(session.state())) + }); + assert_eq!(stale_allocations, 0); + assert!(stale_probe.iterator_laws_hold); + assert_eq!(stale_probe.certificates, 1); + assert_eq!(stale_probe.cells, 2); + assert_eq!(stale_probe.outputs, 1); + assert_eq!(stale_probe.operations, 1); + assert_ne!(stale_probe.checksum, ready_probe.checksum); + assert_eq!( + crate::composition::source_over_evaluation_count(), + stale_compositions + ); + assert_eq!( + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get), + stale_derivations + ); + assert_eq!( + CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get), + stale_assessments + ); + + let black = [Srgb8::new([0; 3])]; + let opposing_backdrops = [ + PackageProgramScenarioV1::new(1, &white), + PackageProgramScenarioV1::new(2, &black), + ]; + session + .update(PackageProgramUpdateV1::Observed { + revision: 3, + scenarios: &opposing_backdrops, + }) + .unwrap(); + let failed_compositions = crate::composition::source_over_evaluation_count(); + let failed_derivations = MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get); + let failed_assessments = CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get); + let (failed_probe, failed_allocations) = crate::test_support::measured_allocations(|| { + consume_package_projection(std::hint::black_box(session.state())) + }); + assert_eq!(failed_allocations, 0); + assert!(failed_probe.iterator_laws_hold); + assert_eq!(failed_probe.certificates, 2); + assert_eq!(failed_probe.cases, 3); + assert_eq!(failed_probe.values, 3); + assert_eq!(failed_probe.provenance, 3); + assert_eq!(failed_probe.cells, 10); + assert_eq!(failed_probe.outputs, 1); + assert_eq!(failed_probe.operations, 1); + assert_eq!(failed_probe.exact_assessments, 5); + assert_eq!(failed_probe.wcag_assessments, 5); + assert_ne!(failed_probe.checksum, stale_probe.checksum); + assert_eq!( + crate::composition::source_over_evaluation_count(), + failed_compositions + ); + assert_eq!( + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get), + failed_derivations + ); + assert_eq!( + CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get), + failed_assessments + ); +} + +#[test] +fn observation_projection_is_invariant_under_every_scenario_permutation_and_keeps_provenance() { + const PERMUTATIONS: [[usize; 3]; 6] = [ + [0, 1, 2], + [0, 2, 1], + [1, 0, 2], + [1, 2, 0], + [2, 0, 1], + [2, 1, 0], + ]; + const IDS: [u32; 3] = [9, 4, 3]; + const BACKDROPS: [[u8; 3]; 3] = [[0xFF; 3], [0x80; 3], [0xFF; 3]]; + + crate::composition::reset_source_over_evaluation_count(); + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(|calls| calls.set(0)); + CORE_PROGRAM_ASSESSMENT_CALLS.with(|calls| calls.set(0)); + + let core_owner = finite_program([[0x80; 3], [0; 3]]); + let content_identity = core_owner.content_identity(); + let package_owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); + let mut core_session = core_owner.instantiate(STREAM).unwrap(); + let mut package_session = package_owner.instantiate(STREAM.value()).unwrap(); + let package_values = BACKDROPS.map(|value| [Srgb8::new(value)]); + let mut first_package_backing = None; + let mut evaluation_counts_after_first = None; + + for permutation in PERMUTATIONS { + let core_update = ObservationUpdateInput { + stream: STREAM, + revision: Revision::new(1), + payload: ObservationPayloadInput::Scenarios(ObservedScenarioSetInput { + scenarios: permutation + .iter() + .map(|index| ScenarioInput { + id: ScenarioId::new(IDS[*index]), + bindings: vec![SurfaceInputBinding::new( + SURFACE_PORT, + signal(BACKDROPS[*index]), + )], + }) + .collect(), + }), + }; + let SessionState::Ready { + current: core_verified, + } = core_session.update(core_update).unwrap() + else { + panic!("black must pass both deduplicated physical cases"); + }; + let package_scenarios = permutation + .map(|index| PackageProgramScenarioV1::new(IDS[index], &package_values[index])); + let package_state = package_session + .update(PackageProgramUpdateV1::Observed { + revision: 1, + scenarios: &package_scenarios, + }) + .unwrap(); + let Some(PackageProgramCertificateV1::Verified(package_verified)) = + package_state.certificates().next() + else { + panic!("the canonical observation must keep one Verified certificate"); + }; + assert_eq!( + package_verified.content_identity().as_bytes(), + content_identity.as_bytes() + ); + assert_package_observation_matches_core( + package_verified.observation(), + core_verified.report().observation(), + ); + + let projected_cases = package_verified + .observation() + .physical_cases() + .map(|case| { + let values = case + .values() + .map(|value| match value { + PackageProgramSignalV1::Iec61966Srgb8D65(value) => value, + }) + .collect::>(); + let provenance = case + .provenance() + .map(|scenario| scenario.value()) + .collect::>(); + (values, provenance) + }) + .collect::>(); + assert_eq!( + projected_cases, + [ + (vec![Srgb8::new([0x80; 3])], vec![4]), + (vec![Srgb8::new([0xFF; 3])], vec![3, 9]), + ] + ); + + let backing = PackageProgramCertificateV1::Verified(package_verified) + .observation_backing_ptr_for_test(); + match first_package_backing { + None => { + first_package_backing = Some(backing); + evaluation_counts_after_first = Some(( + crate::composition::source_over_evaluation_count(), + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get), + CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get), + )); + } + Some(first) => { + assert_eq!(backing, first); + assert_eq!( + evaluation_counts_after_first, + Some(( + crate::composition::source_over_evaluation_count(), + MODELED_TRISTIMULUS_DERIVATION_CALLS.with(core::cell::Cell::get), + CORE_PROGRAM_ASSESSMENT_CALLS.with(core::cell::Cell::get), + )) + ); + } + } + } +} + #[test] fn concrete_package_bridge_projects_total_ready_and_stale_operations() { let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0x80; 3], [0; 3]])); @@ -335,21 +1328,37 @@ fn concrete_package_bridge_projects_total_ready_and_stale_operations() { assert_eq!(ready.cause_certificate_index(), None); let certificates = ready.certificates().collect::>(); assert_eq!(certificates.len(), 1); + assert!(matches!( + certificates[0], + PackageProgramCertificateV1::Verified(_) + )); + assert_eq!(certificates[0].observation().revision(), 1); + let ready_backing = certificates[0].observation_backing_ptr_for_test(); + let mut operations = ready.operations(); + let Some(PackageProgramOperationV1::Set(set)) = operations.next() else { + panic!("Ready must emit one Set operation"); + }; assert_eq!( - certificates[0].kind(), - PackageProgramCertificateKindV1::Verified + set.output_slot(), + PackageProgramOutputSlotIdV1::new(OUTPUT.value()) ); - assert_eq!(certificates[0].revision(), 1); - let ready_backing = certificates[0].observation_backing_ptr_for_test(); + assert_eq!(set.source(), Srgb8::new([0; 3])); + assert_eq!(set.opacity(), 1.0); assert_eq!( - ready.operations().collect::>(), - [PackageProgramOperationV1::Set { - output_slot: PackageProgramOutputSlotIdV1::new(OUTPUT.value()), - source: Srgb8::new([0; 3]), - opacity: 1.0, - certificate_index: 0, - }] + set.certificate().observation().revision(), + certificates[0].observation().revision() ); + assert_eq!( + set.certificate().content_identity(), + certificates[0].content_identity() + ); + assert_eq!( + PackageProgramCertificateV1::Verified(set.certificate()).observation_backing_ptr_for_test(), + ready_backing + ); + assert!(operations.next().is_none()); + drop(operations); + drop(certificates); let reordered = [ PackageProgramScenarioV1::new(1, &white), @@ -393,18 +1402,33 @@ fn concrete_package_bridge_projects_total_ready_and_stale_operations() { assert_eq!(stale.revision(), Some(2)); let certificates = stale.certificates().collect::>(); assert_eq!(certificates.len(), 1); + assert!(matches!( + certificates[0], + PackageProgramCertificateV1::Verified(_) + )); + assert_eq!(certificates[0].observation().revision(), 1); + let mut operations = stale.operations(); + let Some(PackageProgramOperationV1::Hold(hold)) = operations.next() else { + panic!("Stale must emit one Hold operation"); + }; + assert_eq!( + hold.output_slot(), + PackageProgramOutputSlotIdV1::new(OUTPUT.value()) + ); + assert_eq!( + hold.certificate().observation().revision(), + certificates[0].observation().revision() + ); assert_eq!( - certificates[0].kind(), - PackageProgramCertificateKindV1::Verified + hold.certificate().content_identity(), + certificates[0].content_identity() ); - assert_eq!(certificates[0].revision(), 1); assert_eq!( - stale.operations().collect::>(), - [PackageProgramOperationV1::Hold { - output_slot: PackageProgramOutputSlotIdV1::new(OUTPUT.value()), - certificate_index: 0, - }] + PackageProgramCertificateV1::Verified(hold.certificate()) + .observation_backing_ptr_for_test(), + ready_backing ); + assert!(operations.next().is_none()); } #[test] @@ -425,26 +1449,34 @@ fn concrete_package_bridge_distinguishes_failed_remove_from_failed_hold() { assert_eq!(failed.cause_certificate_index(), Some(0)); let certificates = failed.certificates().collect::>(); assert_eq!(certificates.len(), 1); + assert!(matches!( + certificates[0], + PackageProgramCertificateV1::Conflict(_) + )); + assert_eq!(certificates[0].observation().revision(), 1); + let mut operations = failed.operations(); + let Some(PackageProgramOperationV1::Remove(remove)) = operations.next() else { + panic!("Failed without previous evidence must emit one Remove operation"); + }; assert_eq!( - certificates[0].kind(), - PackageProgramCertificateKindV1::Conflict - ); - assert_eq!(certificates[0].revision(), 1); - assert_eq!( - failed.operations().collect::>(), - [PackageProgramOperationV1::Remove { - output_slot: PackageProgramOutputSlotIdV1::new(OUTPUT.value()), - }] + remove.output_slot(), + PackageProgramOutputSlotIdV1::new(OUTPUT.value()) ); + assert!(operations.next().is_none()); let owner = PackageProgramOwnerV1::from_compiled(finite_program([[0; 3], [0xFF; 3]])); let mut session = owner.instantiate(12).unwrap(); - session + let previous = session .update(PackageProgramUpdateV1::Observed { revision: 1, scenarios: &white_only, }) .unwrap(); + let previous_backing = previous + .certificates() + .next() + .unwrap() + .observation_backing_ptr_for_test(); let both = [ PackageProgramScenarioV1::new(1, &white), PackageProgramScenarioV1::new(2, &black), @@ -461,20 +1493,36 @@ fn concrete_package_bridge_distinguishes_failed_remove_from_failed_hold() { assert_eq!( certificates .iter() - .map(|certificate| (certificate.kind(), certificate.revision())) + .map(|certificate| match certificate { + PackageProgramCertificateV1::Verified(value) => { + ("verified", value.observation().revision()) + } + PackageProgramCertificateV1::Conflict(value) => { + ("conflict", value.observation().revision()) + } + }) .collect::>(), - [ - (PackageProgramCertificateKindV1::Conflict, 2), - (PackageProgramCertificateKindV1::Verified, 1), - ] + [("conflict", 2), ("verified", 1)] + ); + let mut operations = failed.operations(); + let Some(PackageProgramOperationV1::Hold(hold)) = operations.next() else { + panic!("Failed with previous evidence must emit one Hold operation"); + }; + assert_eq!( + hold.output_slot(), + PackageProgramOutputSlotIdV1::new(OUTPUT.value()) + ); + assert_eq!(hold.certificate().observation().revision(), 1); + assert_eq!( + hold.certificate().content_identity(), + certificates[1].content_identity() ); assert_eq!( - failed.operations().collect::>(), - [PackageProgramOperationV1::Hold { - output_slot: PackageProgramOutputSlotIdV1::new(OUTPUT.value()), - certificate_index: 1, - }] + PackageProgramCertificateV1::Verified(hold.certificate()) + .observation_backing_ptr_for_test(), + previous_backing ); + assert!(operations.next().is_none()); } #[test] diff --git a/crates/labcolors-core/src/program_session.rs b/crates/labcolors-core/src/program_session.rs index a755d754..a017e6b9 100644 --- a/crates/labcolors-core/src/program_session.rs +++ b/crates/labcolors-core/src/program_session.rs @@ -524,6 +524,15 @@ where } } +#[cfg(test)] +thread_local! { + /// Counts the concrete production evaluator dispatch itself, so certificate + /// projection cannot accidentally recompute a verdict while still reusing + /// the stored physical and modeled witnesses. + pub(crate) static CORE_PROGRAM_ASSESSMENT_CALLS: core::cell::Cell = + const { core::cell::Cell::new(0) }; +} + /// Generates the code-owned heterogeneous evaluator set as parallel closed /// unions. Each evidence variant retains the concrete evaluator's physical + /// LCS binding, identity, release, capability, invocation, measurement, and @@ -572,6 +581,8 @@ macro_rules! define_core_program_evaluators_v1 { modeled_lcs: ModeledLcsOccurrenceV1, invocation: Self::Invocation, ) -> ProgramConstraintAssessmentResultV1 { + #[cfg(test)] + CORE_PROGRAM_ASSESSMENT_CALLS.with(|calls| calls.set(calls.get() + 1)); match invocation { $(CoreProgramConstraintInvocationV1::$variant(invocation) => { let evaluator: $evaluator = $evaluator_value; diff --git a/crates/labcolors-core/tests/package_bridge_red.rs b/crates/labcolors-core/tests/program_boundary.rs similarity index 69% rename from crates/labcolors-core/tests/package_bridge_red.rs rename to crates/labcolors-core/tests/program_boundary.rs index 0e7b6469..a804b279 100644 --- a/crates/labcolors-core/tests/package_bridge_red.rs +++ b/crates/labcolors-core/tests/program_boundary.rs @@ -1,29 +1,31 @@ -//! RED contract for the sole concrete Core package seam. +//! External compile-and-runtime contract for the sole concrete Core Program seam. //! //! This integration crate deliberately has no access to Core-private generic -//! evaluator/session machinery. It must compile using only one hidden, -//! concrete package module once that seam is linked after the P3 + weak-owner -//! rebase. +//! evaluator/session machinery. Every reachable path uses only the closed +//! concrete boundary types. + +use core::iter::FusedIterator; use labcolors_core::Srgb8; use labcolors_core::package_bridge::{ PackageProgramAppearanceContextErrorKindV1, PackageProgramAppearanceContextFieldV1, - PackageProgramAppearanceContextV1, PackageProgramCertificateV1, + PackageProgramAppearanceContextV1, PackageProgramAssessmentV1, PackageProgramCertificateV1, PackageProgramCompileErrorHandleV1, PackageProgramCompileErrorKindV1, PackageProgramCompileErrorV1, PackageProgramConstraintIdV1, PackageProgramDraftErrorV1, PackageProgramDraftV1, PackageProgramInstantiateErrorV1, PackageProgramJointChoiceV1, - PackageProgramJointOrderErrorV1, PackageProgramJointStateV1, + PackageProgramJointOrderErrorV1, PackageProgramJointStateV1, PackageProgramModeledPointV1, PackageProgramNumericDomainErrorV1, PackageProgramOccurrenceIdV1, PackageProgramOpacityInputIdV1, PackageProgramOperationV1, PackageProgramOutputSlotIdV1, - PackageProgramOwnerV1, PackageProgramPaintIdV1, PackageProgramScenarioV1, - PackageProgramSessionV1, PackageProgramSourceIdV1, PackageProgramStateKindV1, - PackageProgramStateViewV1, PackageProgramSurfaceIdV1, PackageProgramSurfaceInputPortIdV1, - PackageProgramSurroundV1, PackageProgramTargetCandidateIdV1, PackageProgramTargetCandidateV1, - PackageProgramTargetIdV1, PackageProgramUpdateErrorKindV1, PackageProgramUpdateV1, + PackageProgramOwnerV1, PackageProgramPaintIdV1, PackageProgramPhysicalPointV1, + PackageProgramScenarioV1, PackageProgramSessionV1, PackageProgramSignalV1, + PackageProgramSourceIdV1, PackageProgramStateKindV1, PackageProgramStateViewV1, + PackageProgramSurfaceIdV1, PackageProgramSurfaceInputPortIdV1, PackageProgramSurroundV1, + PackageProgramTargetCandidateIdV1, PackageProgramTargetCandidateV1, PackageProgramTargetIdV1, + PackageProgramUpdateErrorKindV1, PackageProgramUpdateV1, PackageProgramVerdictV1, }; use labcolors_core::wcag22::Wcag22CriterionV1; -fn exact_size(iterator: I) -> I { +fn exact_size(iterator: I) -> I { iterator } @@ -56,33 +58,102 @@ fn assert_projection_is_linear(view: PackageProgramStateViewV1<'_>) { let certificates = exact_size(view.certificates()); let certificate_count = certificates.len(); for certificate in certificates { - let _: PackageProgramCertificateV1<'_> = certificate; + let _: &[u8; 32] = certificate.content_identity().as_bytes(); + let observation = certificate.observation(); + let _stream_id = observation.stream().value(); + let _revision = observation.revision(); + for port in exact_size(observation.surface_input_ports()) { + let _: PackageProgramSurfaceInputPortIdV1 = port; + } + for case in exact_size(observation.physical_cases()) { + for value in exact_size(case.values()) { + let PackageProgramSignalV1::Iec61966Srgb8D65(value) = value; + let _: Srgb8 = value; + } + for scenario in exact_size(case.provenance()) { + let _ = scenario.value(); + } + } + macro_rules! inspect_cell { + ($cell:expr) => {{ + let cell = $cell; + let _ = cell.case_index(); + let _ = cell.constraint().value(); + let _ = cell.occurrence().value(); + let _ = cell.mode(); + let assessment = cell.assessment(); + let _: PackageProgramVerdictV1 = assessment.verdict(); + match assessment { + PackageProgramAssessmentV1::ExactSrgb8(evidence) => { + let _: Srgb8 = evidence.expected(); + } + PackageProgramAssessmentV1::Wcag22Srgb8(evidence) => { + let _ = evidence.profile_id(); + let _ = evidence.criterion(); + let _ = evidence.foreground_luminance(); + let _ = evidence.background_luminance(); + let _ = evidence.numerical_evidence(); + } + } + let binding = assessment.binding(); + let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(physical) = + binding.physical(); + let _ = physical.subject_paint().value(); + let _ = physical.backdrop_surface().value(); + let _: Srgb8 = physical.subject(); + let _ = physical.opacity(); + let _: Srgb8 = physical.backdrop(); + let _: Srgb8 = physical.visible(); + let PackageProgramModeledPointV1::Iec61966Srgb8ToCie1931TwoDegreeXyzD65RelativeY1( + modeled, + ) = binding.modeled(); + let _: [f64; 3] = modeled.xyz(); + let context = modeled.appearance_context(); + let _ = context.adapting_luminance_cd_m2(); + let _ = context.background_luminance_ratio_yb_yw(); + let _ = context.surround(); + }}; + } + match certificate { + PackageProgramCertificateV1::Verified(verified) => { + let _ = verified.selected_state_index(); + for cell in exact_size(verified.cells()) { + inspect_cell!(cell); + } + for output in exact_size(verified.outputs()) { + let _ = output.output_slot().value(); + let _ = output.paint().value(); + let _: Srgb8 = output.source(); + let _ = output.opacity(); + } + } + PackageProgramCertificateV1::Conflict(conflict) => { + let _ = conflict.considered_state_count(); + for cell in exact_size(conflict.cells()) { + let _ = cell.state_index(); + inspect_cell!(cell); + } + } + } } for operation in exact_size(view.operations()) { match operation { - PackageProgramOperationV1::Set { - output_slot, - source, - opacity, - certificate_index, - } => { - let _: PackageProgramOutputSlotIdV1 = output_slot; - let _: Srgb8 = source; - assert!(opacity.is_finite() && (0.0..=1.0).contains(&opacity)); - assert!(certificate_index < certificate_count); + PackageProgramOperationV1::Set(set) => { + let _: PackageProgramOutputSlotIdV1 = set.output_slot(); + let _: Srgb8 = set.source(); + assert!(set.opacity().is_finite() && (0.0..=1.0).contains(&set.opacity())); + let _ = set.certificate().content_identity(); } - PackageProgramOperationV1::Remove { output_slot } => { - let _: PackageProgramOutputSlotIdV1 = output_slot; + PackageProgramOperationV1::Remove(remove) => { + let _: PackageProgramOutputSlotIdV1 = remove.output_slot(); } - PackageProgramOperationV1::Hold { - output_slot, - certificate_index, - } => { - let _: PackageProgramOutputSlotIdV1 = output_slot; - assert!(certificate_index < certificate_count); + PackageProgramOperationV1::Hold(hold) => { + let _: PackageProgramOutputSlotIdV1 = hold.output_slot(); + let _ = hold.certificate().content_identity(); } } } + let _ = certificate_count; } #[allow(dead_code)] @@ -104,7 +175,7 @@ fn owner_expiry_is_a_closed_package_error( } #[test] -fn red_contract_is_linked_by_the_concrete_package_module() { +fn external_boundary_uses_only_closed_concrete_types() { // Reaching this test means the external crate compiled without importing // Program, evaluator traits, Session, or numeric generations. assert_eq!(core::mem::size_of::(), 3); @@ -244,15 +315,15 @@ fn external_authoring_lowers_the_actual_closed_program_and_returns_canonical_inp }) .unwrap(); assert_eq!(ready.kind(), PackageProgramStateKindV1::Ready); - assert_eq!( - ready.operations().collect::>(), - [PackageProgramOperationV1::Set { - output_slot: output, - source: Srgb8::new([0; 3]), - opacity: 1.0, - certificate_index: 0, - }] - ); + let mut operations = ready.operations(); + let Some(PackageProgramOperationV1::Set(set)) = operations.next() else { + panic!("Ready must emit one Set operation"); + }; + assert_eq!(set.output_slot(), output); + assert_eq!(set.source(), Srgb8::new([0; 3])); + assert_eq!(set.opacity(), 1.0); + assert_eq!(set.certificate().observation().revision(), 1); + assert!(operations.next().is_none()); } #[test] @@ -294,15 +365,81 @@ fn every_physical_constructor_and_both_remaining_constraint_modes_execute() { }) .unwrap(); assert_eq!(state.kind(), PackageProgramStateKindV1::Ready); + let Some(PackageProgramCertificateV1::Verified(certificate)) = state.certificates().next() + else { + panic!("a fixed target must produce one Verified certificate"); + }; + assert_eq!(certificate.selected_state_index(), None); + let mut operations = state.operations(); + let Some(PackageProgramOperationV1::Set(set)) = operations.next() else { + panic!("Ready must emit one Set operation"); + }; + assert_eq!(set.output_slot(), PackageProgramOutputSlotIdV1::new(12)); + assert_eq!(set.source(), Srgb8::new([0; 3])); + assert_eq!(set.opacity(), 1.0); + assert_eq!(set.certificate().observation().revision(), 1); + assert!(operations.next().is_none()); +} + +#[test] +fn certificate_and_set_retain_the_same_nonunit_opacity() { + let source = PackageProgramSourceIdV1::new(1); + let target = PackageProgramTargetIdV1::new(2); + let opacity = PackageProgramOpacityInputIdV1::new(3); + let solid = PackageProgramPaintIdV1::new(4); + let translucent = PackageProgramPaintIdV1::new(5); + let input = PackageProgramSurfaceInputPortIdV1::new(6); + let surface = PackageProgramSurfaceIdV1::new(7); + let occurrence = PackageProgramOccurrenceIdV1::new(8); + let constraint = PackageProgramConstraintIdV1::new(9); + let output = PackageProgramOutputSlotIdV1::new(10); + let context = + PackageProgramAppearanceContextV1::try_new(64.0, 0.2, PackageProgramSurroundV1::Average) + .unwrap(); + let mut draft = PackageProgramDraftV1::new(); + draft.push_source(source, Srgb8::new([0; 3])); + draft.push_fixed_target(target, source); + draft.push_surface_input_port(input); + draft.push_opacity_input(opacity, 0.5); + draft.push_solid_paint(solid, target); + draft.push_opacity_paint(translucent, solid, opacity); + draft.push_input_surface(surface, input); + draft.push_source_over_occurrence(occurrence, translucent, surface, context); + draft.push_exact_hard(constraint, occurrence, Srgb8::new([0x80; 3])); + draft.push_output(output, translucent); + + let owner = draft.compile().unwrap(); + let mut session = owner.instantiate(17).unwrap(); + let white = [Srgb8::new([0xFF; 3])]; + let scenarios = [PackageProgramScenarioV1::new(1, &white)]; + let state = session + .update(PackageProgramUpdateV1::Observed { + revision: 1, + scenarios: &scenarios, + }) + .unwrap(); + let Some(PackageProgramCertificateV1::Verified(certificate)) = state.certificates().next() + else { + panic!("the exact emitted midpoint must be verified"); + }; + let PackageProgramAssessmentV1::ExactSrgb8(assessment) = + certificate.cells().next().unwrap().assessment() + else { + panic!("the authored exact constraint must retain Exact evidence"); + }; + let PackageProgramPhysicalPointV1::EncodedSrgb8SourceOver(physical) = + assessment.binding().physical(); + assert_eq!(physical.opacity().to_bits(), 0.5_f64.to_bits()); + assert_eq!(physical.visible(), Srgb8::new([0x80; 3])); assert_eq!( - state.operations().collect::>(), - [PackageProgramOperationV1::Set { - output_slot: PackageProgramOutputSlotIdV1::new(12), - source: Srgb8::new([0; 3]), - opacity: 1.0, - certificate_index: 0, - }] + certificate.outputs().next().unwrap().opacity().to_bits(), + physical.opacity().to_bits() ); + + let Some(PackageProgramOperationV1::Set(set)) = state.operations().next() else { + panic!("the verified output must emit one Set"); + }; + assert_eq!(set.opacity().to_bits(), physical.opacity().to_bits()); } #[test] diff --git a/scripts/verify_point_support_surplus.py b/scripts/verify_point_support_surplus.py index 608c6cdb..084cfd3b 100755 --- a/scripts/verify_point_support_surplus.py +++ b/scripts/verify_point_support_surplus.py @@ -58,7 +58,7 @@ SOURCE_BINDING_LAW = "point-support-rust-whole-file-semantic-cone-v2" SOURCE_BINDING_DOMAIN = b"labcolors.point-support.rust-whole-file-semantic-cone.v2" EXPECTED_SOURCE_CAPSULE_SHA256 = ( - "246c77dbf4923aca4cdaedb12be910ceb2885c94e4e42f652cdcba9b9417a427" + "b25636d4ae969d4a93a82a1324fb4445ba4861a51f18a85c4a0a062afcfbbfda" ) EXPECTED_Q55_PROOF_SHA256 = ( "ac59cf89503170c789223b91d775213a19d4e571ef930f2ea609fcd51b14defd"