From c3a553e7fc232871ef50521666c319ed0700a201 Mon Sep 17 00:00:00 2001 From: ruv Date: Wed, 17 Jun 2026 23:28:39 -0400 Subject: [PATCH] =?UTF-8?q?perf(photonlayer):=20M1=20=E2=80=94=20cached=20?= =?UTF-8?q?+=20in-place=20Propagator=20(1.70x,=20bit-identical)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hot-path optimization for the mask-learning loop, which propagates thousands of fields through one fixed config. The config-only transfer function H was recomputed on every call, and every propagate() cloned the field buffer. - Propagator precomputes H once per (config,w,h); propagate_into() runs the forward FFT -> xH -> inverse FFT in place (no per-call clone). - Output is bit-for-bit identical to the free propagate() (asserted in cached_propagator_is_bit_identical, always-on). - Measured 1.70x over the naive path at 64x64 x3000 (release): naive=615ms -> cached+inplace=361ms. Proof is an --ignored timing test (debug wall-clock is meaningless); correctness gate runs in the default suite. Also lands: - ADR-263 PhotonLayer FiberGate (transmission-matrix MMF backend; receipt- verified, NOT zero-knowledge; non-square T; nalgebra column-major contract). - docs/research/photonlayer/APPLICATIONS.md — task-trained-sensors positioning, application areas, viral demos, product path, platform acceptance test. Co-Authored-By: claude-flow --- crates/photonlayer-core/src/lib.rs | 2 +- crates/photonlayer-core/src/propagate.rs | 122 +++++++++-- .../tests/propagation_speedup.rs | 88 ++++++++ ...otonlayer-fibergate-transmission-matrix.md | 206 ++++++++++++++++++ docs/research/photonlayer/APPLICATIONS.md | 61 ++++++ 5 files changed, 461 insertions(+), 18 deletions(-) create mode 100644 crates/photonlayer-core/tests/propagation_speedup.rs create mode 100644 docs/adr/ADR-263-photonlayer-fibergate-transmission-matrix.md create mode 100644 docs/research/photonlayer/APPLICATIONS.md diff --git a/crates/photonlayer-core/src/lib.rs b/crates/photonlayer-core/src/lib.rs index 8bfece432..9831bb087 100644 --- a/crates/photonlayer-core/src/lib.rs +++ b/crates/photonlayer-core/src/lib.rs @@ -62,7 +62,7 @@ pub mod prelude { accuracy, compression_ratio, frame_spectrum_embedding, input_frame_similarity, mse, psnr, MetricReport, }; - pub use crate::propagate::propagate; + pub use crate::propagate::{propagate, Propagator}; pub use crate::receipt::{build_receipt, verify_receipt, ExperimentReceipt, Provenance}; pub use crate::simulator::{OpticalSimulator, ScalarSimulator, SimulationTrace}; } diff --git a/crates/photonlayer-core/src/propagate.rs b/crates/photonlayer-core/src/propagate.rs index 8c6b646b4..319ddf787 100644 --- a/crates/photonlayer-core/src/propagate.rs +++ b/crates/photonlayer-core/src/propagate.rs @@ -66,19 +66,17 @@ enum TransferKind { AngularSpectrum, } -fn transfer_fn(field: &OpticalField, config: &OpticalConfig, kind: TransferKind) -> Result { - let (w, h) = (field.width, field.height); +/// Build the config-only transfer function H (length `w*h`, row-major). H depends +/// solely on (w, h, λ, z, d, kind) — never on the field — so it can be computed +/// once and reused across many propagations (see [`Propagator`]). +fn transfer_kernel(w: usize, h: usize, config: &OpticalConfig, kind: TransferKind) -> Vec { let lambda = config.wavelength_m(); let z = config.distance_m(); let d = config.pixel_pitch_m(); - let fx = fftfreq(w, d); let fy = fftfreq(h, d); - - let mut data = field.data.clone(); - fft_2d(&mut data, w, h, false); - let k = 2.0 * PI / lambda; + let mut hk = vec![Complex::ZERO; w * h]; for row in 0..h { for col in 0..w { let fxx = fx[col]; @@ -86,8 +84,7 @@ fn transfer_fn(field: &OpticalField, config: &OpticalConfig, kind: TransferKind) let h_val = match kind { TransferKind::Fresnel => { // Drop constant exp(i k z); keep quadratic phase. - let phase = -PI * lambda * z * (fxx * fxx + fyy * fyy); - Complex::from_phase(phase) + Complex::from_phase(-PI * lambda * z * (fxx * fxx + fyy * fyy)) } TransferKind::AngularSpectrum => { let arg = 1.0 - (lambda * fxx).powi(2) - (lambda * fyy).powi(2); @@ -99,17 +96,108 @@ fn transfer_fn(field: &OpticalField, config: &OpticalConfig, kind: TransferKind) } } }; - let idx = row * w + col; - data[idx] = data[idx] * h_val; + hk[row * w + col] = h_val; + } + } + hk +} + +/// Apply a precomputed transfer kernel: forward FFT → ×H → inverse FFT. +fn apply_transfer(field: &OpticalField, w: usize, h: usize, hk: &[Complex]) -> OpticalField { + let mut data = field.data.clone(); + fft_2d(&mut data, w, h, false); + for (dv, hv) in data.iter_mut().zip(hk.iter()) { + *dv = *dv * *hv; + } + fft_2d(&mut data, w, h, true); + OpticalField { width: w, height: h, data } +} + +fn transfer_fn(field: &OpticalField, config: &OpticalConfig, kind: TransferKind) -> Result { + let (w, h) = (field.width, field.height); + let hk = transfer_kernel(w, h, config, kind); + Ok(apply_transfer(field, w, h, &hk)) +} + +/// A precomputed propagation operator. Build once per `(config, width, height)` +/// and reuse across many fields — the config-only transfer function is computed +/// a single time instead of on every call. This is the hot path in mask-learning +/// loops (thousands of propagations share one config). Output is bit-identical to +/// the free [`propagate`] function. +pub struct Propagator { + width: usize, + height: usize, + kind: PropKind, +} + +enum PropKind { + Fraunhofer, + /// Precomputed transfer function H (length `width*height`). + Transfer(Vec), +} + +impl Propagator { + /// Precompute the operator for a fixed grid + config. + pub fn new(width: usize, height: usize, config: &OpticalConfig) -> Result { + if !is_pow2(width) { + return Err(PhotonError::NotPowerOfTwo(width)); + } + if !is_pow2(height) { + return Err(PhotonError::NotPowerOfTwo(height)); + } + let kind = match config.propagation { + PropagationMode::Fraunhofer => PropKind::Fraunhofer, + PropagationMode::Fresnel => { + PropKind::Transfer(transfer_kernel(width, height, config, TransferKind::Fresnel)) + } + PropagationMode::AngularSpectrum => PropKind::Transfer(transfer_kernel( + width, + height, + config, + TransferKind::AngularSpectrum, + )), + }; + Ok(Self { width, height, kind }) + } + + /// Propagate a field through the precomputed operator. + pub fn propagate(&self, field: &OpticalField) -> Result { + if field.width != self.width || field.height != self.height { + return Err(PhotonError::NotPowerOfTwo(field.width)); + } + match &self.kind { + PropKind::Fraunhofer => fraunhofer(field), + PropKind::Transfer(hk) => Ok(apply_transfer(field, self.width, self.height, hk)), } } - fft_2d(&mut data, w, h, true); - Ok(OpticalField { - width: w, - height: h, - data, - }) + /// **In-place** propagation — forward FFT → ×H → inverse FFT, mutating `data` + /// directly (no per-call field clone). Bit-identical to [`Propagator::propagate`]; + /// this is the batch hot path (mask-learning loops over many samples). + pub fn propagate_into(&self, data: &mut [Complex]) -> Result<()> { + let (w, h) = (self.width, self.height); + if data.len() != w * h { + return Err(PhotonError::NotPowerOfTwo(data.len())); + } + match &self.kind { + PropKind::Fraunhofer => { + fft_2d(data, w, h, false); + fftshift_2d(data, w, h); + let norm = 1.0 / (w as f32 * h as f32).sqrt(); + for c in data.iter_mut() { + *c = c.scale(norm); + } + } + PropKind::Transfer(hk) => { + fft_2d(data, w, h, false); + for (dv, hv) in data.iter_mut().zip(hk.iter()) { + *dv = *dv * *hv; + } + fft_2d(data, w, h, true); + } + } + Ok(()) + } } #[cfg(test)] diff --git a/crates/photonlayer-core/tests/propagation_speedup.rs b/crates/photonlayer-core/tests/propagation_speedup.rs new file mode 100644 index 000000000..0771df92b --- /dev/null +++ b/crates/photonlayer-core/tests/propagation_speedup.rs @@ -0,0 +1,88 @@ +//! M1 proof: the cached + in-place `Propagator` is faster than the naive free +//! `propagate()` (which recomputes the transfer function H and clones the field +//! every call), and produces **bit-identical** output. No speedup claim without +//! this measured number. +//! +//! Run: `cargo test -p photonlayer-core --release --test propagation_speedup -- --ignored --nocapture` + +use std::time::Instant; + +use photonlayer_core::config::OpticalConfig; +use photonlayer_core::field::{InputImage, OpticalField}; +use photonlayer_core::propagate::{propagate, Propagator}; + +const N: usize = 64; // grid (learn-loop regime where H-recompute is a large fraction) +const ITERS: usize = 3000; + +fn test_field(n: usize) -> OpticalField { + // Deterministic non-trivial pattern. + let px: Vec = (0..n * n) + .map(|i| { + let (x, y) = ((i % n) as f32, (i / n) as f32); + 0.5 + 0.5 * ((x * 0.3).sin() * (y * 0.2).cos()) + }) + .collect(); + let img = InputImage::from_norm_f32(n, n, px).unwrap(); + OpticalField::from_image(&img, n, n).unwrap() +} + +/// Always-on correctness gate: the cached + in-place path is bit-for-bit +/// identical to the free `propagate()`. Cheap; runs in the default suite. +#[test] +fn cached_propagator_is_bit_identical() { + let field = test_field(N); + let config = OpticalConfig::demo(N, N); + let reference = propagate(&field, &config).unwrap(); + let prop = Propagator::new(N, N, &config).unwrap(); + let via_struct = prop.propagate(&field).unwrap(); + let mut buf = field.data.clone(); + prop.propagate_into(&mut buf).unwrap(); + assert_eq!(via_struct.data, reference.data, "Propagator::propagate must match free propagate"); + assert_eq!(buf, reference.data, "propagate_into must be bit-identical to free propagate"); +} + +/// Timing proof (M1). Release-only — wall-clock is meaningless in debug. Run: +/// `cargo test -p photonlayer-core --release --test propagation_speedup -- --ignored --nocapture` +#[test] +#[ignore = "timing benchmark — run with --release --ignored"] +fn cached_propagator_is_faster() { + let field = test_field(N); + let config = OpticalConfig::demo(N, N); + + // Warm up. + for _ in 0..64 { + let _ = propagate(&field, &config).unwrap(); + } + + // Naive: free propagate (recompute H + clone) every call. + let t = Instant::now(); + let mut sink = 0.0f32; + for _ in 0..ITERS { + let out = propagate(&field, &config).unwrap(); + sink += out.data[0].re; + } + let naive = t.elapsed().as_secs_f64(); + + // Optimized: build operator once; in-place propagate into a reused buffer. + let prop = Propagator::new(N, N, &config).unwrap(); + let mut scratch = vec![photonlayer_core::complex::Complex::ZERO; N * N]; + let t = Instant::now(); + for _ in 0..ITERS { + scratch.copy_from_slice(&field.data); + prop.propagate_into(&mut scratch).unwrap(); + sink += scratch[0].re; + } + let opt = t.elapsed().as_secs_f64(); + std::hint::black_box(sink); + + let speedup = naive / opt; + eprintln!( + "propagation {N}x{N} x{ITERS}: naive={:.1}ms cached+inplace={:.1}ms speedup={speedup:.2}x", + naive * 1e3, + opt * 1e3 + ); + assert!( + speedup >= 1.5, + "cached+in-place propagator must be >= 1.5x the naive path; got {speedup:.2}x" + ); +} diff --git a/docs/adr/ADR-263-photonlayer-fibergate-transmission-matrix.md b/docs/adr/ADR-263-photonlayer-fibergate-transmission-matrix.md new file mode 100644 index 000000000..45e30b709 --- /dev/null +++ b/docs/adr/ADR-263-photonlayer-fibergate-transmission-matrix.md @@ -0,0 +1,206 @@ +--- +adr: 263 +title: "PhotonLayer FiberGate — transmission-matrix optical compression for drift-bound privacy verification" +status: proposed +date: 2026-06-18 +authors: [ruvnet, claude-flow] +related: [ADR-260, ADR-261, ADR-262] +tags: [photonlayer, fiber-optics, multimode-fiber, transmission-matrix, mmf, privacy, receipts, drift, ruvector, wasm] +--- + +# ADR-263 — PhotonLayer FiberGate + +> **Decision in one line.** Add a **multimode-fiber (MMF) propagation backend** to +> `photonlayer-core` based on a **calibrated complex transmission matrix (T)**, a +> deterministic intensity-sensor projection, **drift-aware calibration receipts**, +> and ruVector-backed calibration memory — moving PhotonLayer from free-space +> diffraction to guided-wave optics. + +## Context + +PhotonLayer today models **free-space** scalar diffraction (Fresnel / Fraunhofer / +angular-spectrum; ADR-260). The cutting edge of optical computing is moving to +**guided-wave** substrates — e.g. the Fiber-based Diffractive Deep Neural Network +(Fiber-D2NN, *Opt. Lett.* 50(17):5254) shows high ML accuracy from linear optical +relations **inside** fibers. + +The right framing (and the bounded claim we will defend): + +> **PhotonLayer Fiber treats the multimode fiber as a calibrated, drifting, +> complex *linear* optical operator. The learned mask shapes the input field so +> that the drifting fiber + sensor produce task-useful compressed measurements.** + +This is stronger than "the fiber is just another propagation model." Below +nonlinear power thresholds, the input→output **field** relationship of an MMF is +well modeled by a transmission matrix **T**; the observed **sensor** image is an +intensity speckle pattern. Both deep-learning and intensity-matrix methods are +established for MMF image recovery/classification (arXiv:1805.05134). Bending and +temperature **drift** of T is the central real-world challenge and requires +recalibration (Nat. Commun. s42005-023-01410-x). + +### Claim hygiene (no slop) + +- **This is NOT zero-knowledge.** The design proves a private input was transformed + under a specific mask + calibrated fiber state into a specific low-dimensional + measurement. That is a **cryptographic receipt**, not a ZK proof. We use: + **receipt-verified privacy gate** / **non-reconstructive verification** / + **optical biometric commitment** / **fiber-bound biometric proof**. Safe claim: + *"the server verifies a receipt bound to the current fiber calibration and + receives only the compressed optical measurement, not the source image."* +- **Consented verification only** — no gallery identification. + +## Decision + +Add a fiber backend and the supporting machinery, in this **build order** (the +moat is the physics + receipts + memory, not the browser): + +1. `photonlayer-core` fiber backend (T-matrix propagation + deterministic sensor). +2. Fiber **drift** benchmark in `photonlayer-bench`. +3. ruVector **calibration memory** in `photonlayer-ruvector`. +4. `photonlayer-wasm` client bindings. + +### Mathematical model + +**Level 1 — practical T-matrix simulator (implementation target):** + +``` +E_in' = E_in ⊙ e^{iΦ} (apply learned phase mask) +E_out = T · E_in' (linear mode mixing; T may be non-square) +I_sensor = B · |E_out|² + ε (intensity-only, binned, deterministic seeded noise ε) +``` + +**Level 2 — mode-basis simulator (later, for physical interpretability):** + +``` +E(x,y,z) = Σ_m a_m(z) ψ_m(x,y) e^{iβ_m z} +a_m(0) = ∬ E_in(x,y) e^{iΦ(x,y)} ψ_m*(x,y) dx dy +a(L) = C(L, θ, T_env) a(0) (separates ideal propagation from drift) +``` + +### Rust design (determinism-first) + +Three correctness rules the implementation MUST honor: + +1. **Layout contract** — `nalgebra::DMatrix` is column-major; define and document a + fixed (row-major image) ⇄ (column-vector) mapping so flatten/reshape never + silently reorders the spatial layout. +2. **Non-square T** — input modes, output field samples, and sensor bins have + different dimensions (e.g. 256 → 64 → 4). `T` is `output_len × input_len`. +3. **Determinism is not free from `nalgebra`** — pin operation order, no + uncontrolled parallel reductions, finite checks on every value, stable + serialization. Bit-identical output across Linux/macOS/WASM is an invariant + (ADR-261). + +Core type (shape per the corrected design): + +```rust +pub struct FiberTransmissionMatrix { + pub t: DMatrix, // output_len × input_len (non-square allowed) + pub input_len: usize, + pub output_len: usize, + pub version: u64, + pub calibration_hash: [u8; 32], +} +// propagate(input_field, mask) -> FiberOutput { field, intensity }, +// validating input/mask length + matrix shape + finiteness (FiberError otherwise). +``` + +New `photonlayer-core` modules: `fiber.rs`, `fiber_matrix.rs`, +`fiber_calibration.rs`, `fiber_sensor.rs`, `fiber_receipt.rs`. Core structs: +`FiberTransmissionMatrix`, `FiberCalibrationState`, `FiberPilotPattern`, +`FiberDriftModel`, `FiberSensorProjector`, `FiberReceipt`. + +### Drift-aware training (turns drift from liability into a training distribution) + +``` +min_{Φ,θ} E_{T ~ D_fiber} [ L(decoder(B|T(E ⊙ e^{iΦ})|²), y) ] + λ R(Φ) + γ L_privacy +``` + +Train the mask against a **family** of likely T states, not one matrix. `R(Φ)` = +smoothness/manufacturability; `L_privacy` = reconstruction/leakage penalty. + +Drift metric and accuracy-vs-drift tracking: + +``` +Δ_T = ‖T_t − T_{t−1}‖_F / ‖T_{t−1}‖_F A = f(Δ_T, SNR, bins, mask) +``` + +### ruVector calibration memory (`photonlayer-ruvector`) + +Store each calibration as an experiment object (fiber_id, t_version, +calibration_hash, timestamp, temperature, bend_state, snr_db, drift_norm, +mask_id, task, eer, reconstruction_score) and use ruVector for: nearest-calibration +lookup, drift-regime clustering, recalibration prediction, finding masks robust +across multiple T states, and spectral failure explanation. + +### Receipt schema + commitment + +```rust +pub struct FiberReceipt { + pub photonlayer_version: String, + pub fiber_id_hash: [u8; 32], + pub t_version: u64, + pub t_hash: [u8; 32], + pub pilot_hash: [u8; 32], + pub phase_mask_id: [u8; 32], + pub input_commitment: [u8; 32], // salted: C = H(input_hash || nonce || purpose || session) + pub output_hash: [u8; 32], + pub decoder_id: [u8; 32], + pub nonce: [u8; 32], + pub timestamp_ms: u64, +} +``` + +Key design choice: **never store the raw biometric hash in a reusable form** — use +the salted commitment `C`. + +### Threat model + +| Threat | Risk | Mitigation | +|---|---|---| +| Replay old N-pixel output | Medium | bind receipt to T version + nonce + timestamp | +| T-matrix theft | Medium | rotate calibration, encrypt at rest, bind to device | +| Reconstruction attack | High | publish attack suite (ADR-262), train privacy penalty | +| Server spoofing T state | Medium | signed calibration state | +| Browser config DoS | Medium | existing input validation | +| Biometric misuse | High | consented verification only, no gallery | +| Model inversion | High | leakage tests vs attributes + identity | + +## Consequences + +### Positive +- Unlocks the flagship **medical-endoscope** (optical compression inside a needle- + thin fiber bundle — no bulky tip sensor) and **drone/edge** sensing paths. +- Drift becomes an **anti-replay signal**, not only a liability. +- The defensible moat: **drift-aware, receipt-verified optical compression with + experiment memory** — not the browser layer. + +### Negative / risks +- Real fiber validation needs hardware + calibration; until then this is a + **simulator + receipt schema**, claimed as such. +- T-matrix calibration is a new operational burden (pilot wavefronts, drift + thresholds, recalibration triggers). +- Determinism across native + WASM with `nalgebra` complex linear algebra requires + care (see rule 3). + +### Neutral +- Free-space backend (ADR-260) stays; fiber is an additional `PropagationKind`. + +## Acceptance tests + +1. Same input + mask + T + seed → identical output hash across Linux, macOS, WASM. +2. Learned mask beats random by ≥ **20 pp** on compressed fiber classification. +3. EER stays below target across ≥ **5 drift states**. +4. Reconstruction-attack similarity stays below the documented threshold. +5. Receipt verification **fails** if any of {T version, phase mask, decoder, nonce, + output, sensor config} changes. +6. Recalibration trigger fires when `Δ_T` crosses the configured threshold. +7. **Non-square** T passes end-to-end: 256 inputs → 64 output samples → 4 sensor bins. + +## Links +- ADR-260 (free-space simulator), ADR-261 (mask exchange & determinism), + ADR-262 (privacy-preserving optical verification). +- Fiber-D2NN — *Opt. Lett.* 50(17):5254. MMF + deep learning — arXiv:1805.05134. + Single-ended T recovery / drift — *Nat. Commun.* s42005-023-01410-x. +- Product: **PhotonLayer FiberGate** — calibrated fiber as a physically-bound, + receipt-verified transformation layer for non-reconstructive verification. diff --git a/docs/research/photonlayer/APPLICATIONS.md b/docs/research/photonlayer/APPLICATIONS.md new file mode 100644 index 000000000..46ccf5b22 --- /dev/null +++ b/docs/research/photonlayer/APPLICATIONS.md @@ -0,0 +1,61 @@ +# PhotonLayer — Applications & Strategy + +> **The category:** not cameras, not neural nets, not "optical computing" — **task-trained sensors**. +> Strategic thesis: *AI created an infinite appetite for visual data; PhotonLayer goes the other way — +> **capture less, decide faster, leak less, and prove what happened.*** + +Companion to [ASSESSMENT.md](ASSESSMENT.md), ADR-260/261/262/263. Bounded-claim discipline applies: +no diagnosis claims, consented verification only, "receipt-verified" (not zero-knowledge). + +## The core shift — image recognition without images + +``` +Traditional: scene → camera image → neural net → decision +PhotonLayer: scene → trained optical transform → tiny sensor measurement → small decoder → decision +``` + +Recognize / verify / classify / reject from a **compressed optical signature** instead of a stored +image. Aligned with learned optical encoders, lensless privacy cameras, meta-optics, and hybrid +optical-electronic neural nets (arXiv:2406.04129; ACS Photonics 5c02358; PMC12011376). + +> **The system sees enough to decide, but not enough to reconstruct.** + +## Application areas (positioned by market readiness) + +| Area | PhotonLayer role | First market? | +|---|---|---| +| **Industrial inspection** | defect yes/no, barcode/label verify, tamper, scratch, sorting | **Yes — best first market** (low regulatory burden, clear ROI, controlled lighting) | +| Privacy-first machine vision | face *verification* without storage, occupancy without identity, PPE/posture, child-safe presence | High viral / commercial distinctiveness | +| Ultra-low-bandwidth sensors | tiny sensors, always-on vision, few-bin event verification, battery/edge | Reduces the expensive part of edge AI (pixel movement) | +| Drone / robotics pre-perception | landing-pad, horizon, obstacle, marker, motion-cue, terrain class | Medium (safer than full autonomy) | +| Medical imaging **research** | microscopy morphology, lesion compression, endoscopy, cell pre-sort, pathology triage | High risk — **research only, no diagnosis** | +| Fiber-bound security | tamper-evident links, device-bound auth, anti-replay, drift-liveness (ADR-263) | New product class | +| Scientific instruments | design instruments around the *question*, not the image | Biggest long-term idea | + +## A new benchmark lane — "accuracy per captured photon / pixel / sensor bin" + +Metrics: accuracy/sensor-pixel · accuracy/digital-MAC · EER under reconstruction constraint · drift +robustness · receipt reproducibility · calibration half-life · leakage score · failure boundary. The +receipt system is what makes these **reproducible optical experiments**, not just claims. + +## Viral demos (for the Pages UI) + +- **A — "The camera that cannot see you"**: face → 4-px measurement → same/different verdict. *It verified the person without storing the face.* +- **B — "The microscope learned what not to measure"**: full image → optical compression → morphology class → failed reconstruction. *The useful signal survived. The image did not.* +- **C — "Drone vision in 4 pixels"**: landing marker detected from a few optical bins. *The drone needs a decision, not a frame.* +- **D — "The fiber is the lock"** (ADR-263): verification fails when T drifts or the receipt is replayed. *The cable became part of the cryptographic boundary.* + +## Product path (build order) + +1. **PhotonLayer Studio** — browser optical-mask simulator with receipts. +2. **PhotonLayer Bench** — public optical-compression benchmark suite. +3. **PhotonLayer PrivacyGate** — consented verification + reconstruction attacks. +4. **PhotonLayer Industrial** — defect/barcode inspection SDK. +5. **PhotonLayer FiberGate** — drift-aware MMF simulator + lab bridge (ADR-263). +6. **PhotonLayer BioResearch** — microscopy/dermatology research simulator (no diagnostic claims). + +## Sharp acceptance test (platform-grade) + +> On **3 public datasets**: learned optical mask ≥ full-image baseline − 2%; sensor pixels reduced +> ≥ **16×**; digital MACs reduced ≥ **10×**; reconstruction-attack similarity below threshold; +> receipt verification reproducible across Rust-native **and** WASM.