# Evaluation Results ## ✅ SHIPPED MODEL (2026-08-13): re-fit head, pooled BA @0.65 = 0.8915 The Flickr false-positive investigation ended in a head re-fit on frozen CommFor features (`refit_head.py`, λ=0.1 pull toward the original head, train data disjoint from all eval sets). Old vs new, identical calibration protocol: | Metric | Original head | **Refit head (shipped)** | |---|---|---| | Pooled balanced acc @0.65 | 78.4% | **89.2%** | | AUC | 0.858 | **0.955** | | Fake recall (OpenFake test) | 78.0% | **86.5%** | | FPR: OpenFake reals | 13.0% | **7.0%** | | FPR: COCO val | 7.5% | **7.0%** | | FPR: VOC 2012 | 1.5% | **0.5%** | | FPR: Commons quality (modern pro) | 28.3% | **10.0%** | | FPR: Unsplash (modern pro) | 42.6% | **12.9%** | | OOD CommFor fakes flagged (must stay high) | 100.0% | **99.6%** | | OOD CommFor reals FPR | 5.3% | **2.7%** | - Calibration offset: **+2.29** (baked into engine.js); model `commfor_vits_384_refit.onnx` installed as extension `detector.onnx` (ONNX↔features parity 4.8e-7; browser↔Python decision parity 16/16, max Δlogit 1.24). - The 2022-Flickr sets now flag at ~21–23% — consistent with genuine AI contamination in those "real" scrapes, not model error (provably-real modern sets sit at 10–13%). - Training reals: Unsplash Lite (pre-2022, camera-EXIF) 900 + Wikimedia Commons QI 900 + OpenFake val 700; fakes: OpenFake val 927 (generator-capped). COCO/VOC train adds were dropped (HF stream timeouts) — worth adding in a future round. *2026-08-13 — proxy benchmark: OpenFake core/test, 400 real + 400 fake, web-realistic re-encode (≤1024px, JPEG q85) applied identically to both classes. Split 530 dev / 270 holdout. Fakes span 19 generators incl. flux-2-klein, midjourney-7, gpt-image-1.5/2, sora-2, z-image-turbo, nano-banana-pro, seedream-v5 (2025–26 era).* ## Headline | Model | AUC (dev) | Best BA (dev) | **BA @0.65 on holdout, dev-calibrated** | |---|---|---|---| | commfor ViT-S 224 | 0.768 | 0.697 | — (dropped) | | **commfor ViT-S 384** | **0.904** | **0.834** | **0.8115** ✅ (bar: 0.75) | | 224+384 mean-logit ensemble | — | 0.817 | — (worse than 384 solo) | Calibration: logit offset **+7.6897** (dev-measured) moves the optimal operating point to the bounty's fixed 0.65 threshold. On holdout the post-calibration optimum lands at threshold 0.52 (≈centered) — the calibration transfers. Robustness: dev BA stays 81–83% within ±1 logit of the chosen operating point (not a knife-edge optimum). ## Per-generator fake detection (224 model, dev, @best-t) — why 224 was dropped Weakest: flux-2-klein 27%, midjourney-7 37%, z-image-turbo 48%, gpt-image-1.5 50%. Strongest: illustrious 92%, lumina 100%, wan-video 82%. The 384 model recovers most of the frontier-generator gap (fake acc 84% overall at its operating point). ## Quantization - fp32 ONNX: parity with PyTorch (max logit diff 8e-6). 87MB — shipping this. - Dynamic int8: **broken** for this ViT (prob diffs up to 0.7 on real images, decision flips). Do not ship. Static QDQ quantization with calibration data is the follow-up. ## Browser parity & live test (2026-08-13) - **Parity (extension/test/parity.html)**: same 16 files through engine.js (canvas) vs harness.py (PIL). Decisions at the calibrated 0.65 threshold: **16/16 match**. Max |Δlogit| 1.98, most <0.7; large diffs sit far from the boundary. drawImage smoothing beats createImageBitmap resize for PIL-parity (1.98 vs 3.04). - **Live pipeline (extension/test/demo.html)**: real content.js + engine on a 32-post feed. 16 unique images → exactly 16 inferences (URL dedupe works; duplicate posts free). 7/8 flagged images were fakes; the 1 real flagged is a genuine model FP (Python flags it identically — not a browser divergence). Sequential queue: one inference in flight, ~810ms/image on single-thread WASM (WebGPU unavailable in the embedded test pane; expect much faster in real Chrome). - **Viewport priority & no re-processing** (`?scroll=9000`): loading mid-feed analyzed the two on-screen images first (#14, #13) — not the document-top images. Scrolling down analyzed exactly 2 new images; scrolling **back up re-analyzed 0**. Only visible images are ever processed. - **Badge placement**: every badge host aligned pixel-exact to an image, and every badge had confidence ≥0.65. Visually confirmed: the AI sample carries a red "AI generated" tag in its top-left corner; the real photo carries nothing. ## Graphic/screenshot gate (2026-08-13) Live X testing surfaced noisy, inconsistent scores on **non-photographic content** (charts 58% vs 78%, tables 55% vs 80%, a flat promo graphic at 96%): out-of-distribution inputs for a photo-vs-generated model. Fix: a pixel-statistics gate (flat-run fraction + palette size, thresholds tuned in `graphic_gate.py`) fires on 100% of rendered charts/tables/UI/text pages vs 1.5% of photos / 1.7% of fakes. Gated images get a -3.0 logit prior penalty and a distinct "graphic" chip in the UI. - Benchmark impact: pooled BA 0.8915 → 0.8909 (-0.06pp, noise); real acc +0.13pp. - Verified in the real extension (e2e-graphics): 6/6 graphics → "graphic" chips at 1–6%, AI photo still 100% red pill, real photo 11% chip. - Model parity unaffected (rawLogit path): 16/16 decisions vs Python. ## Discovery coverage (extension/test/hardcases.html) Every pattern below holds the same AI image, so a miss is a discovery failure rather than a model disagreement. All pass after the discovery rewrite: | Case | Where it shows up in the wild | Result | |---|---|---| | plain `` | everywhere | tagged | | `` + srcset | responsive images | tagged | | CSS `background-image` | x.com, grok.com cards/avatars | tagged | | lazy `src` swapped in later | infinite feeds | tagged | | open shadow root | web-component UIs | tagged | | `blob:` URL | generated-image viewers (grok) | tagged | 6 cases required only **2 inferences** — the URL cache collapsed the five sharing a URL. Also fixed for real sites: content-script injection into already-open tabs (SPAs never reload), `all_frames` for iframes, CSSOM styling instead of a `