Adversarial Illuminant Synthesis
Finds a physically realizable light that makes a safe palette confusable.
The problem
A palette validated under one illuminant is only safe under that illuminant. Metamerism means a set of materials that separate cleanly under D65 can become confusable when the light changes, and nobody checks the worst case. Published adversarial color work attacks CNNs in pixel space; this tool asks the harder question of what the worst realizable light is for a human observer looking at your actual palette.
The method
The candidate illuminant is parametrized as shifted Chebyshev coefficients synthesized onto the canonical 380 to 750 nm grid, clamped to non-negative power, and normalized to 100 at 560 nm so results compare directly to CIE illuminants. A seeded (1+1) evolution strategy with 1/5-success-rule step adaptation then minimizes the palette's smallest pairwise Oklab delta E as perceived under the candidate light, optionally routed through the engine's Machado (2009) CVD simulation for protan, deutan, or tritan observers. The objective rejects degenerate near-dark lights with a total-power floor, since darkness trivially collapses everything and is not a meaningful attack. This builds on the framing that existing adversarial color work targets CNNs in pixel space, while this attacks a human-observer colorimetric system with a realizable light source.
What it promises
- The attack SPD is physically realizable: non-negative power at every wavelength on the canonical 38-sample grid, normalized to 100 at 560 nm for direct comparison with CIE illuminants.
- Degenerate near-dark lights are rejected by a total-power floor, so the reported collapse comes from spectral shape, not from turning the lights off.
- The response reports baseline and attacked minimum delta E (Oklab), the degradation factor between them, and exactly which pair of palette entries collapsed.
- Fully deterministic given a seed, and it fails loudly: tristimulus palette input is refused with INVALID_ARG because a color has no unique spectrum to attack.
Calling it
{
"tool": "synthesize_adversarial_spectrum",
"arguments": {
"palette": [
{
"space": "spd",
"values": [
0.85,
0.6,
0.25,
0.1
],
"wavelengths_nm": [
380,
500,
620,
750
]
},
{
"space": "spd",
"values": [
0.12,
0.35,
0.7,
0.88
],
"wavelengths_nm": [
380,
500,
620,
750
]
},
{
"space": "spd",
"values": [
0.4,
0.75,
0.5,
0.2
],
"wavelengths_nm": [
380,
500,
620,
750
]
}
],
"baseline_illuminant": "D65",
"observer": {
"type": "deutan",
"severity": 1
},
"order": 8,
"iterations": 2000,
"seed": 42
}
} Clone the repo, build with cd mcp && npm install && npm run build, then register: claude mcp add maryslab -- node <repo>/mcp/dist/server.js
Known limits
- The optimizer is a seeded stochastic search, so the returned light is a found attack, an existence witness rather than a proven worst case. For guaranteed bounds, use the companion certificate tool.
- Realizable here means non-negative spectral power on the canonical grid. The tool does not check whether a lamp emitting this exact SPD is manufacturable or commercially available.
- Separation is measured as minimum pairwise delta E in Oklab under one viewing condition per run: one baseline illuminant (D65, D50, A, or E) and at most one optional CVD observer profile.