Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions .cargo/mutants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
# КАДЕНС: по расписанию, НЕ per-PR (мутация дорога — каждый мутант = полный прогон
# набора). Запускается ночным workflow `.github/workflows/mutation.yml`.
#
# СКОУП: параметр-свободное ЯДРО МАТЕМАТИКИ — самый критичный, детерминированный,
# включая exact alpha/composite и typed numerical decisions. Гигантские
# config.rs/solve.rs/semantic.rs НЕ
# мутируются по умолчанию (часы прогона, малая маржинальная ценность на orchestration-
# коде). Полный workspace-прогон — отдельный ручной/расширенный запуск (см. workflow).
# СКОУП: детерминированное ядро математики и границы correctness-evidence:
# exact alpha/composite, typed numerical decisions, observation admission и
# revision-bound recheck. Гигантские config.rs/solve.rs/semantic.rs не мутируются
# по умолчанию (часы прогона, малая маржинальная ценность). Полный workspace-прогон
# — отдельный ручной/расширенный запуск (см. workflow).
#
# ЧТО ПРОВЕРЯЕТ: что property/golden/characterization тесты РЕАЛЬНО кусаются —
# выживший мутант = молчаливая дыра в наборе (coverage=пол, mutation=правда).
Expand All @@ -16,12 +16,15 @@ examine_globs = [
"crates/labcolors-core/src/analog.rs",
"crates/labcolors-core/src/appearance.rs",
"crates/labcolors-core/src/composition.rs",
"crates/labcolors-core/src/constraints/mod.rs",
"crates/labcolors-core/src/constraints/exact.rs",
"crates/labcolors-core/src/glow.rs",
"crates/labcolors-core/src/material.rs",
"crates/labcolors-core/src/numerical_plan.rs",
"crates/labcolors-core/src/numerics.rs",
"crates/labcolors-core/src/observation.rs",
"crates/labcolors-core/src/pair.rs",
"crates/labcolors-core/src/recheck.rs",
"crates/labcolors-core/src/srgb8.rs",
"crates/labcolors-core/src/wcag22.rs",
"crates/labcolors-core/src/wcag22_evidence.rs",
Expand Down
64 changes: 35 additions & 29 deletions crates/labcolors-core/src/analog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,14 @@
use crate::Srgb8;
use crate::appearance::{
PhysicalProgramIdentityV1, PointOpacityOverSurfaceV1, ProgramOccurrenceBindingV1,
ResolvedOccurrence, SourceOverCertificateV1,
ResolvedOccurrence, SourceOverCertificateV1, VisiblePointBindingV1,
};
use crate::constraints::{
ExactConstraintIdentityV1, ExactIdentityMismatchV1, ExactSrgb8IdentityV1,
BoundAssessment, BoundVerdict, ExactConstraintIdentityV1, ExactIdentityAssessmentV1,
ExactIdentityCapabilityV1, ExactIdentityMismatchV1, ExactIdentityReleaseV1,
ExactSrgb8IdentityV1, assess,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ExactIdentityCapabilityV1 {
FinalOccurrenceSrgb8IdentityV1,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ExactIdentityReleaseV1 {
V1,
}

/// Opaque identity authored invocation-а. Standalone helper не притворяется
/// client binding; named compiler назначает ordinal конкретной декларации.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand All @@ -49,7 +41,14 @@ pub(crate) struct ExactIdentityEvidenceV1 {
pub(crate) struct VerifiedAlphaAnalogV1 {
occurrence: ResolvedOccurrence,
authored: AuthoredAlphaBindingIdV1,
target: Srgb8,
assessment: BoundAssessment<
VisiblePointBindingV1,
ExactConstraintIdentityV1,
ExactIdentityReleaseV1,
ExactIdentityCapabilityV1,
Srgb8,
ExactIdentityAssessmentV1,
>,
}

impl VerifiedAlphaAnalogV1 {
Expand All @@ -66,16 +65,18 @@ impl VerifiedAlphaAnalogV1 {
}

pub(crate) fn evidence(&self) -> ExactIdentityEvidenceV1 {
let assessment = *self.assessment.outcome();
let binding = *self.assessment.binding();
ExactIdentityEvidenceV1 {
physical: ExactAlphaProgramV1::physical_identity(),
authored: self.authored,
constraint: ExactAlphaProgramV1::constraint_identity(),
capability: ExactIdentityCapabilityV1::FinalOccurrenceSrgb8IdentityV1,
release: ExactIdentityReleaseV1::V1,
program_occurrence: self.occurrence.program_occurrence_binding(),
occurrence: *self.occurrence.certificate(),
target: self.target,
actual: Srgb8::new(self.occurrence.visible()),
constraint: *self.assessment.identity(),
capability: *self.assessment.capability(),
release: *self.assessment.release(),
program_occurrence: binding.program_occurrence(),
occurrence: binding.occurrence(),
target: *self.assessment.invocation(),
actual: assessment.actual(),
}
}
}
Expand Down Expand Up @@ -254,10 +255,6 @@ impl ExactAlphaProgramV1 {
PointOpacityOverSurfaceV1::physical_identity()
}

pub(crate) const fn constraint_identity() -> ExactConstraintIdentityV1 {
ExactSrgb8IdentityV1::IDENTITY
}

pub(crate) fn evaluate(
authored: AuthoredAlphaBindingIdV1,
target: Srgb8,
Expand All @@ -269,15 +266,24 @@ impl ExactAlphaProgramV1 {
.map_err(|error| {
ExactAlphaProgramErrorV1::InvalidOpacity(error.message().to_owned())
})?;
let assessment = ExactSrgb8IdentityV1::evaluate(&occurrence, target)
.map_err(ExactAlphaProgramErrorV1::IdentityMismatch)?;
debug_assert_eq!(assessment.target(), assessment.actual());
let assessment = match assess(&occurrence, &ExactSrgb8IdentityV1, target) {
BoundVerdict::Pass(assessment) => assessment,
BoundVerdict::Fail(failure) => {
return Err(ExactAlphaProgramErrorV1::IdentityMismatch(
failure.into_outcome(),
));
}
};
debug_assert_eq!(assessment.outcome().target(), assessment.outcome().actual());
let verified = VerifiedAlphaAnalogV1 {
occurrence,
authored,
target: assessment.target(),
assessment,
};
debug_assert_eq!(verified.evidence().actual, assessment.actual());
debug_assert_eq!(
verified.evidence().actual,
verified.assessment.outcome().actual()
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Ok(verified)
}
}
Expand Down
40 changes: 28 additions & 12 deletions crates/labcolors-core/src/appearance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -895,9 +895,20 @@ impl PointOpacityOverSurfaceV1 {
opacity: f64,
backdrop: [u8; 3],
) -> Result<ResolvedOccurrence, PointOpacityError> {
crate::composition::validate_alpha(opacity)
.map_err(|message| PointOpacityError { message })?;
let opacity = if opacity == 0.0 { 0.0 } else { opacity };
let opacity =
crate::composition::AdmittedOpacityV1::new(opacity).map_err(|_| PointOpacityError {
message: format!("alpha вне конечного [0,1]: {opacity}"),
})?;
Ok(Self::evaluate_admitted(source, opacity, backdrop))
}

/// Исполнение после typed admission alpha. Этим входом final recheck
/// исключает невозможную повторную numeric validation и stringly error.
pub(crate) fn evaluate_admitted(
source: [u8; 3],
opacity: crate::composition::AdmittedOpacityV1,
backdrop: [u8; 3],
) -> ResolvedOccurrence {
let mut paints = [None; 2];
let mut surfaces = [None; 2];
let mut occurrences = [None; 1];
Expand All @@ -911,16 +922,16 @@ impl PointOpacityOverSurfaceV1 {
_ => unreachable!("sealed point program has one SurfaceInput port"),
},
|id| match id {
POINT_OPACITY => opacity,
POINT_OPACITY => opacity.value(),
_ => unreachable!("sealed point program has one OpacityInput port"),
},
&mut paints,
&mut surfaces,
&mut occurrences,
);
Ok(occurrences[0].unwrap_or_else(|| {
occurrences[0].unwrap_or_else(|| {
unreachable!("compiler-verified point program materializes its Occurrence")
}))
})
}
}

Expand Down Expand Up @@ -1098,15 +1109,13 @@ impl ResolvedOccurrence {
self.visible
}

#[cfg(test)]
pub(crate) fn modeled_srgb8_point(&self) -> ModeledSrgb8PointOccurrence {
ModeledSrgb8PointOccurrence {
visible: self.visible,
backdrop: self.backdrop,
}
}

#[cfg(test)]
pub(crate) fn visible_point_binding(&self) -> VisiblePointBindingV1 {
VisiblePointBindingV1 {
program_occurrence: self.program_occurrence_binding(),
Expand All @@ -1131,18 +1140,23 @@ impl ResolvedOccurrence {
/// Ссылки на occurrence/certificate здесь нет: evaluator структурно не может
/// подменить скомпозитированный stimulus authored source-цветом.
#[derive(Debug, Clone, Copy)]
#[cfg(test)]
pub(crate) struct ModeledSrgb8PointOccurrence {
visible: [u8; 3],
backdrop: [u8; 3],
}

#[cfg(test)]
impl ModeledSrgb8PointOccurrence {
pub(crate) fn visible(self) -> [u8; 3] {
self.visible
}

#[cfg_attr(
not(test),
expect(
dead_code,
reason = "shipped exact evaluator reads visible; backdrop is consumed by the test-private WCAG adapter"
)
)]
pub(crate) fn backdrop(self) -> [u8; 3] {
self.backdrop
}
Expand All @@ -1152,17 +1166,19 @@ impl ModeledSrgb8PointOccurrence {
/// proof. Assessment не может пережить смену Paint/Surface/alpha лишь потому,
/// что финальные байты случайно совпали.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(test)]
pub(crate) struct VisiblePointBindingV1 {
program_occurrence: ProgramOccurrenceBindingV1,
occurrence: SourceOverCertificateV1,
}

#[cfg(test)]
impl VisiblePointBindingV1 {
pub(crate) fn program_occurrence(self) -> ProgramOccurrenceBindingV1 {
self.program_occurrence
}

pub(crate) fn occurrence(self) -> SourceOverCertificateV1 {
self.occurrence
}
}

/// Полный атомарный результат evaluate в каноническом typed-ID порядке.
Expand Down
3 changes: 2 additions & 1 deletion crates/labcolors-core/src/appearance_graph_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::appearance::{
CompositionProfileV1, OccurrenceId, OccurrenceSpec, OpacityInputId, PaintId, PaintSpec,
SurfaceId, SurfaceInputPortId, SurfaceSpec,
};
use crate::constraints::Evaluator;

const SOURCE: ColorInputId = ColorInputId::new(0);
const OTHER_SOURCE: ColorInputId = ColorInputId::new(2);
Expand Down Expand Up @@ -90,7 +91,7 @@ fn static_exact_program_is_declarative_topology_plus_typed_constraint() {
crate::appearance::PhysicalProgramIdentityV1::SolidOpacityOverSurfaceEncodedSrgb8V1
);
assert_eq!(
crate::analog::ExactAlphaProgramV1::constraint_identity(),
crate::constraints::ExactSrgb8IdentityV1.identity(),
crate::constraints::ExactConstraintIdentityV1::FinalSrgb8IdentityV1
);
}
Expand Down
46 changes: 41 additions & 5 deletions crates/labcolors-core/src/composition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,44 @@
//!
//! Модуль не знает solver, recipe, constraint или client ID. Он фиксирует
//! единственную физическую операцию encoded-sRGB8 source-over, чтобы proposal,
//! appearance runtime и final-emission gate не могли разойтись по арифметике.
//! appearance runtime и revision-bound recheck не могли разойтись по арифметике.

/// Typed отказ admission straight alpha. Диагностический transport-текст
/// строится только legacy façade-ом; нижняя физика не хранит stringly error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OpacityAdmissionErrorV1 {
NonFinite,
OutsideUnitInterval,
}

/// Канонический straight alpha внутри конечного `[0,1]`.
///
/// Значение хранится битами: `-0.0` понижается в единственный физический `+0.0`
/// state, а все остальные binary64 значения сохраняются точно.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct AdmittedOpacityV1(u64);

impl AdmittedOpacityV1 {
pub(crate) fn new(alpha: f64) -> Result<Self, OpacityAdmissionErrorV1> {
if !alpha.is_finite() {
return Err(OpacityAdmissionErrorV1::NonFinite);
}
if !(0.0..=1.0).contains(&alpha) {
return Err(OpacityAdmissionErrorV1::OutsideUnitInterval);
}
let canonical = if alpha == 0.0 { 0.0 } else { alpha };
Ok(Self(canonical.to_bits()))
}

#[cfg(test)]
pub(crate) const fn bits(self) -> u64 {
self.0
}

pub(crate) const fn value(self) -> f64 {
f64::from_bits(self.0)
}
}

/// Порядок binary64-операций совпадает с официальным JS-потребителем на
/// непрозрачной подложке. Expanded-форма запрещена: два округления нарушают
Expand All @@ -16,10 +53,9 @@ pub(crate) fn source_over_channel_srgb8(tint: u8, alpha: f64, backdrop: u8) -> u
}

pub(crate) fn validate_alpha(alpha: f64) -> Result<(), String> {
if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
return Err(format!("alpha вне конечного [0,1]: {alpha}"));
}
Ok(())
AdmittedOpacityV1::new(alpha)
.map(|_| ())
.map_err(|_| format!("alpha вне конечного [0,1]: {alpha}"))
}

pub(crate) fn source_over_srgb8(
Expand Down
Loading
Loading