Phone-first, viewing-condition-aware psychovisual data collection for zensim. Pre-registered study of how a condition-conditioned quality metric can close zensim's SROCC plateau on real web content.
Existing public IQA datasets (KADID-10k, TID2013, CID22, AIC-3) bake fixed viewing conditions into their labels: lab monitor, specified viewing distance, specified ambient. zensim trained on them plateaus around SROCC 0.82 on CID22, and the residual is structured by viewing conditions β DPR, intrinsic-to-device-pixel ratio, viewing distance, ambient, gamut. Squintly collects pairwise + threshold judgments with those conditions recorded as first-class data per trial, on the phones that actually deliver the bulk of web image traffic.
Two outputs, both consumed by zensim training:
- Per-encoding latent quality
ΞΈ_{s,e,c}(BT-Davidson scale, anchored to CID22 via shared anchors) β replaces zensim's training labels and adds a condition vector. - Threshold function
q_threshold(c, k)fork β {notice, dislike, hate}β what an encoder picker actually wants: "minimize bytes subject toq β₯ q_threshold(c, k)".
| Code | Rust+axum backend, Vite+vanilla-TS frontend embedded via rust-embed, SQLite via sqlx, deploy via Railway + Docker. |
| Live | https://squintly.imazen.org β all four flows work: rating (180 imazen26-v4-test sources (imazen-26 TEST split only) across 21 strata / 4320 encodings across libjpeg-turbo, jpegli, libwebp, libavif, served straight from public R2 β see DEPLOY.md Β§15), curator (903 R2 candidates), calibration, suggest. Confirm which build you are looking at with curl .../api/export/pareto.manifest.json. |
| Study | v0.2 protocol pre-registered in docs/STUDY.md β H1βH5 hypotheses, holdout discipline, decision rules for zensim v48 ship locked. |
| Jargon | New to IQA? Start with docs/GLOSSARY.md β every term, unit, method, and stat in this README, in plain English, with links to the underlying papers. |
| Phase | v0.2 finishing β ASAP active sampler wired into next_trial (Β§3 amplifier #1). Playwright e2e is green across 4 device projects incl. Z Fold 7 cover/inner (117 tests, with a per-screen layout-viewport guard in e2e/layout.spec.ts), plus a scripted demo-user audit (just audit) screenshotting every screen. Remaining: the same walkthrough on a physical phone + nightly per-observer Ξ· batch (#9). |
| Pilot | Planned: 100 sessions once v0.2 ships (Β§8 phases). |
# Build the embedded frontend + the binary.
just build
# 1. Build a real corpus from imazen-26 and serve trials from it
# (~10 min; 32 sources across all four size buckets, 4 real codecs).
# See DEPLOY.md Β§15 for what the selection guarantees.
python3 scripts/build_demo_corpus.py --out ~/tmp/squintly-corpus --clean
cargo run -- --coefficient-path ~/tmp/squintly-corpus --bind 127.0.0.1:3030
# 2. Or point at any other coefficient SplitStore / HTTP viewer:
cargo run -- --coefficient-path /path/to/coefficient/benchmark-results --port 3030
cargo run -- --coefficient-http http://localhost:8081 --port 3030
# 3. Open http://localhost:3030 in any browser, on any device.
# Calibrate (credit-card slider), pass the qualifier, rate trials.
# 4. Export when done (all TSV in zenanalyze schema):
curl http://localhost:3030/api/export/pareto.tsv > pareto_human.tsv
curl http://localhost:3030/api/export/thresholds.tsv > thresholds.tsv
curl http://localhost:3030/api/export/responses.tsv > responses_raw.tsv
curl http://localhost:3030/api/export/unified.tsv > unified.tsv
# 5. Smoke test (gates v0.2 done):
cargo test --test smokeDeploy to Railway: see DEPLOY.md. Dockerfile builds the embedded TS,
then the Rust binary, then ships a debian:bookworm-slim runtime β no Node at
runtime.
The study is pre-registered in docs/STUDY.md. One-paragraph
summary:
Train zensim-v48 on squintly's
(stimulus, viewing_condition_bin)JOD labels and see whether it beats v47-strict (the current production bake) on held-out squintly data by β₯ 0.05 SROCC, with the gain concentrated in the high-DPR / short-viewing- distance bins, without regressing on CID22 / AIC-3 / AIC-4. Threshold functionq_threshold(c, k)must achieve RΒ² β₯ 0.5 on held-out for at leastk = notice. Decision rule for shipping v48 tozensim::Profile::Ais locked in Β§3 of STUDY.md.
Methodology references: docs/methodology.md (squintly's
internal methods), docs/participant-grading.md,
docs/motivation-and-compensation.md. For the
broader reproduction-grade methods reference (BT-MLE math, active-sampling info
criteria, software inventory) see the zenpapers reference book.
Ranked by ROI for converged-cells-per-human-hour. Status legend: β done Β· π‘ partial (impl exists, not fully wired) Β· π΅ planned Β· β« explicitly out of scope.
| # | Lever | Status | Where | Plan |
|---|---|---|---|---|
| 1 | Active sampling (ASAP EIG) β pick next pair by expected information gain | β | src/asap.rs, src/sampling.rs (select_pair_with_eig), src/handlers.rs (enhance_pair_with_asap), tests/asap_wire.rs |
Wired into next_trial for non-golden pairs: on each pair request, refit BT-Davidson (Ο_prior=1.0) over historical pair responses for the source and pick the highest-EIG adjacent non-trivial pair. Falls back to the random adjacent pair when comparisons < ASAP_MIN_OBS=8. Validate the β₯ 5Γ speedup-vs-random H4 prediction in pilot. |
| 2 | Viewing-conditions captured per trial (DPR, intrinsic-to-device, calibrated CSS-px/mm, ambient, distance, gamut) | β | migrations/0001_init.sql, web/src/conditions.ts, web/src/calibration.ts |
Display is a hard 1:1 device pixels minimum β never scaled down; anything larger is panned (docs/methodology.md Β§2). intrinsic_to_device_ratio is 1.0 by construction, and pan_*/visible_* (migration 0012) record how much was seen. |
| 3 | Staircase thresholds (Levitt 1971 transformed upβdown, 79.4 / 70.7 / 50 % for notice / dislike / hate) | β | src/staircase.rs (233 LOC) |
tune step-halving cadence after pilot |
| 4 | Trivial-triplet filter β pre-filter pairs the metric ensemble unanimously predicts (β₯ 95 % preference); humans never see them | β | docs/methodology.md Β§3.4, src/sampling.rs |
verify firing rate in pilot |
| 5 | Anchor reservation β CID22-style reference-pinned anchors at ~5 % rate align our scale across studies | β | migrations/0006_v02_rigor.sql (corpus_anchors), docs/methodology.md Β§3.6 |
populate the table from CID22 source overlap |
| 6 | Onboarding calibration β codec-probe + credit-card slider for physical CSS-px/mm | β | web/src/calibration-onboarding.ts, web/src/codec-probe.ts, migrations/0004_codec_support.sql |
β |
| 7 | Honeypots β known-answer trials at ~1/30 rate; fail-two-end-session | β | docs/methodology.md Β§3.9 |
tune fail rate to β€ 5 % on T1 observers |
| 8 | Per-session grading AβF + session_weight β {0, 0.5, 1.0, 1.5} multiplier at scale reconstruction |
β | src/grading.rs, migrations/0002_grading.sql |
β |
| 9 | Per-observer reliability β nightly observer_grades aggregation (trial-weighted golden, session-count-weighted even-odd, geometric-mean composite weight, trusted-pool promotion at weight β₯ 0.70 β§ n_trials β₯ 50); Crowd-BT Ξ· + pwcmp LOO log-likelihood queued |
π‘ | src/grading.rs::rebuild_observer_grades, hooked into a 24h tokio task at src/main.rs |
implement the pwcmp LOO (dist_L > 1.5) + PΓ©rez-Ortiz 2019 (Ξ΄_o, Ο_o) ACR fits post-pilot once we have β₯ 50 sessions/observer signal |
| 10 | Engagement / retention β streaks, freezes, tier ladder (anon β email β passkey β researcher), leaderboard | β | migrations/0003_engagement.sql, migrations/0005_auth.sql, web/src/auth-modal.ts |
iterate UX based on drop-off telemetry |
| 11 | Phone-first single-stimulus + tap interaction β short attention sessions, no mouse, portrait orientation | β | SPEC.md Β§"Target audience: phones", Playwright zfold7-cover/inner device profiles |
β |
| 12 | Unified pairwise + ACR-rating scale (PΓ©rez-Ortiz 2019 mixed BT-Davidson + ordinal model) | β | src/bt.rs, src/unified.rs (solver fixed 2026-05-28 β see CLAUDE.md resolved-bug log), docs/methodology.md Β§5.5 |
fit_unified converges on consistent pair+rating evidence (regression-tested via unified_competitive_with_bt_only_on_heldout_pairs). pair_log_likelihood / rating_log_likelihood / total_log_likelihood shipped as the H3 evaluation entry point. The 2-nats/trial H3 gate itself is the pilot's job, not a unit test. |
| 13 | Predictive sampling (PS-PC) β offline classifier marks pair candidates predict vs defer using a metric ensemble (CVVDP / butteraugli / ssim2-gpu / zensim / PaQ-2-PiQ); humans only see defer |
π΅ | not yet | v0.3 add: classifier runs as a one-shot pre-flight, populates defer queue in sampling.rs. Expected: 8β22 % defer ratio at Ξ· β {0.97, 0.995} (Mohammadi 2311.03850). |
| 14 | Boosted Triplet Comparison (BTC) β quadratic boosting h(d) = Ξ³βd + Ξ³βdΒ² for high-fidelity near-JND sensitivity |
π΅ | not yet | v0.3 optional add for the q β₯ 80 band; expected ~3Γ discrimination (AIC-3 2410.09501) |
| 15 | Hybrid expert + crowd β route highest-EIG pairs to expert raters | β« | not in scope for v1 (squintly is anonymous by design); zensim-level hybrid possible later via a separate study |
The first 11 are done; #1 (ASAPβnext_trial) and #9 (nightly observer_grades batch) both shipped recently. The remaining v0.2 work is the end-to-end smoke test on a real phone plus the deeper #9 follow-up (pwcmp LOO + PΓ©rez-Ortiz 2019 per-observer ACR fits) once we have β₯ 50 sessions/observer to fit on.
ββββββββββββββββββββββββββββββββββββββββββ
β coefficient (image store) β
β HTTP or local SplitStore β
ββββββββββββββββ¬ββββββββββββββββββββββββββ
β src/coefficient.rs (consumer-only)
βββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β Curator workflow β β Trial workflow β
β /api/curator/* β β /api/session, /api/trial β
β src/curator.rs β β src/handlers.rs β
β Stream β Curate β β β sampler ββΆ next_trial β
β Threshold β Export β β response β grading β β
β β curated_manifest β β weighted BT-Davidson β
ββββββββββββ¬ββββββββββββ ββββββββββββ¬ββββββββββββββββ
β TSV upload β inserts
βΌ βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SQLite (sqlx) β single canonical store β
β observers Β· sessions Β· trials Β· responses Β· staircases Β· corpus_anchorsβ
β curator_candidates Β· curator_decisions Β· curator_size_variants Β· β
β curator_thresholds Β· suggestions Β· calibration_pool β
ββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββ
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββββββ ββββββββββββββββββββββββ
β Online (per β β Nightly batch β β Export TSVs β
β session) β β grading + Crowd-BT Ξ·β β pareto / thresholds / β
β staircase + BT β β (planned, Β§3 #8/#9) β β responses / unified β
β online estimate β βββββββββββββββββββββββ β β zenanalyze/zentrain β
βββββββββββββββββββ ββββββββββββββββββββββββ
Workflows in detail:
- Curator (
docs/CORPUS_CURATOR_SPEC.md) β researchers iterate the corpus offline: stream candidates from R2/local, curate (include/exclude- size bucket), set per-source threshold q β {30, 50, 70, 85, 95}, export
curated_manifest.tsv. Frontend:web/src/curator.ts+curator-encoder.ts. Backend:src/curator.rs+src/variant_gen.rs.
- size bucket), set per-source threshold q β {30, 50, 70, 85, 95}, export
- Suggestion β anyone can submit a content URL via
/api/suggestions; researcher triages via/suggestions/{id}/{accept,reject}. Backend:src/suggestions.rs,src/suggestion_store.rs. Migration:0008_suggestions.sql. - Trial β observer arrives, opt-in calibration, qualifier (8 trials β β₯ 6 correct β
qualifier_passed), main session of mixed Type-S + Type-P (sampler-chosen) with honeypots interleaved. Frontend:web/src/trial.ts. Backend:src/handlers.rs,src/sampling.rs,src/asap.rs(post v0.2-wiring). - Auth β passwordless magic-link via Postmark for T1+ observers
(
src/auth.rs,migrations/0005_auth.sql). Observer aliases across devices (observer_aliases). - Grading β per-session: golden-pass rate, straight-line max, honeypot failures β
session_grade A..Fβsession_weight β {0, 0.5, 1.0, 1.5}. Per-observer: trusted pool, skill score, per-observer reliability Ξ· (planned). - Export β four TSVs in zenanalyze/zentrain
schema:
pareto.tsv(BT-Davidson per-encoding scores),thresholds.tsv(q_notice/dislike/hate per-source-per-condition),responses.tsv(raw per-trial),unified.tsv(joint BT + ACR fit).
| What | Where | Format | Why there |
|---|---|---|---|
| SQLite DB | ${SQUINTLY_DB:-/data/squintly.db} |
sqlx-managed; 9 migrations applied at startup | Single source of truth for human responses; backups + Tower mirror per CLAUDE.md ML rules |
| Source images | coefficient store (HTTP or local SplitStore) |
content-addressed by sha256 | Squintly never writes back to coefficient; consumer-only |
| Variant encodings | served via /api/proxy/encoding/{id}; backed by coefficient |
content-addressed | reuses coefficient's content-addressing |
| Curated manifest snapshots | uploaded R2 JSONL at pub-7c5c57fd3e0842f0b147946928891d40.r2.dev |
one JSONL per snapshot | reproducible curator inputs |
| Export TSVs | downloaded by user; canonical location is whoever downloaded last | TSV (zenanalyze schema) | _MANIFEST.json per export, includes study_commit + db_snapshot_sha256 (planned) |
| Auth tokens | auth_tokens table |
Postmark magic-link tokens, ephemeral | TTL'd; cleaned by cleanup_expired_tokens |
| Tower mirror | /mnt/tower/output/squintly-archive-<study-version>/ |
full SQLite snapshot + image manifest | per CLAUDE.md "Mirror canonical data to Tower NAS BEFORE any cleanup" |
| Table | Purpose | Migration |
|---|---|---|
observers |
UUID + tier + calibration + email + skill_score + grading flags | 0001, 0002, 0003, 0006 |
sessions |
viewing-conditions snapshot, grade, weight, golden-pass-rate, codec-support | 0001, 0002, 0004, 0006 |
trials |
individual trial: source_hash, encoding id, condition vector, type S/P, staircase id | 0001 |
responses |
observer answer + dwell + swap_count + zoom + viewport snapshot at response time | 0001 |
staircases |
per-(source, condition_bucket) staircase state (reversals, current q, exit) | 0001 |
corpus_anchors |
CID22-style reference-pinned anchors for cross-study scale alignment | 0006 |
source_flags |
curator decisions on excluded/flagged sources | 0006 |
calibration_pool + calibration_responses |
shared calibration trials whose answers we know | 0006 |
auth_tokens + observer_aliases |
passwordless auth + multi-device merging | 0005 |
curator_candidates / _decisions / _size_variants / _thresholds |
curator workflow state | 0007, 0009 |
suggestions |
user-submitted content with workflow status | 0008 |
DDL in migrations/. Schema is the source of truth for fields; this
table is a navigation aid.
- Online: per session, BT-Davidson online estimate of
ΞΈ(the BT score) viasrc/bt.rs::fit_session(); cheap, only over the current session's trial pool. - Offline: full unified PΓ©rez-Ortiz 2019 mixed BT-Davidson + ordinal-ACR with
per-observer
(Ξ΄_o, Ο_o); cross-source comparability viacorpus_anchors. Gaussian prior Ο_prior = 1.0 (pwcmp standalone, arXiv 1712.03686; now in zenpapers corpus).
For k β {notice, dislike, hate}:
- Online: per-session staircase reversal-average estimate of
q_kper (source, condition_bucket). - Offline: GAM logistic
P(rating β₯ k | q, c) = Ξ¦((q β ΞΌ_k(c)) / Ο_k(c))with smooths ondpr,intrinsic_to_device_ratio,viewing_distance_cm, codec offsets, per-source random intercept. H2 indocs/STUDY.mdpre-registers RΒ² β₯ 0.5 fork = noticeon held-out.
- Hooks in
src/grading.rs; full Ξ· + pwcmp leave-one-out per-observer log-likelihood is the TODO atsrc/grading.rs:340β343. Plan: nightly batch reading the last 30 days of trials, writingobservers.skill_score+ a per-observerΞ·.
- Bootstrap over observers (1000 resamples) for
ΞΈandq_k. The observer is the unit of resampling because errors are correlated within observer (BT.500 + PΓ©rez-Ortiz). - Laplace / Hessian-inverse for active-sampling EIG (cheap, online); switch to bootstrap for the final reported CIs.
For metric-vs-metric (v48 vs v47):
- Ξ-SROCC with bootstrap 95 % CIs over observers.
- Krasula different-vs-similar AUC.
- F-test on residual variance for nested model comparisons.
- Bonferroni across the 5 pre-registered hypotheses (
Ξ±_individual = 0.05 / 5 = 0.01).
Full math + citations: docs/methodology.md Β§5β7 + the zenpapers
reference book ch3-5_sampling_screening_cis.md.
ML projects accumulate sprawl faster than any other code; we apply the discipline from the global CLAUDE.md "ML Data Pipeline Discipline" section verbatim:
- One canonical DB.
${SQUINTLY_DB}is the only store of human responses. No shadow exports, no "fast-iter" copies in/tmp. Backups via SQLiteVACUUM INTOnightly to/data/snapshots/squintly-<iso>.db. - Every export carries
build_commit./api/export/{kind}.tsvships a sibling/api/export/{kind}.manifest.jsoncarryingbuild_commit(the git SHA the binary was built from, viabuild.rs),schema_version,exported_at,row_count,sha256of the TSV body, and asource_querypointer at the Rust source that produced the rows. The TSV response also carries aLink: <β¦>; rel="describedby"header pointing at the sidecar. Without this, "is this export still valid?" becomes a forensic audit. - No source-image duplication. Squintly consumes the coefficient store; never copies images, never writes back. The R2 manifest at the public bucket is the canonical curator input.
- Tower mirror BEFORE cleanup. Per CLAUDE.md: before deleting any local DB snapshot, sha256-verify a Tower mirror copy. The snapshot directory grows; at ~10 snapshots a one-time consolidate-to-Tower run is fine; never deletes without the mirror.
- Block storage for anything > 30 KB. Curator manifests, exported TSVs, image
archives all live under
/mnt/v/output/squintly/<study-version>/with a pointer file in this repo. Never commit images, never commit the DB. - Cleanup of stale agent worktrees. If multi-agent work creates sibling jj
workspaces,
jj workspace forget+rm -rfsame-day per CLAUDE.md. - Dated experiment dirs OK β€ 7 days; promote or archive by day 14. Any
docs/explorations/<date>-<topic>/either gets promoted into the maindocs/tree or moved to_archive/.
What we don't yet enforce automatically (tracking these as v0.2 exit gates):
-
_MANIFEST.jsonemitted alongside every export TSV (/api/export/{kind}.manifest.jsonpaired with the TSV; describedby Link header). - Nightly Tower-mirror snapshot β
main.rsauto-detects/mnt/towerand spawns a dailyVACUUM INTO /mnt/tower/output/squintly-archive/<iso>.dbtask. Silently no-ops on Railway / CI / remote deploys where the mount is absent. - Per-table row counts in a
db_healthtable updated hourly (src/db_health.rs::refresh+migrations/0010_db_health.sql; hourly tokio task inmain.rs). - Curator R2 snapshot tag pinned in each session β
manifest_snapshotstable (migrations/0011_manifest_snapshots.sql) records(r2_public_base, manifest_path, manifest_sha256, body_bytes, n_candidates)per load; UNIQUE key on the three identity columns means re-loading an unchanged manifest is a cheap no-op.sessions.manifest_snapshot_idFK pins the latest snapshot at session creation so analysis six months later can join from any session to the exact candidate pool it drew from.
Locked sequence; the order matters because each phase depends on the previous one's data:
| Phase | Goal | Done-when | Status |
|---|---|---|---|
| v0.1 | Backend skeleton + trial routes wired; curator + variant generation; license surfacing | cargo test green + curator export round-trips |
β |
| v0.2 | ASAP wired into next_trial; per-session grading active; export _MANIFEST.json; smoke test exercises the full session loop on a real phone |
All 5 of SPEC.md Β§"Hard v0.1 is done when" + the 4 sprawl-enforcement checkboxes above | π‘ in progress |
| v0.3 β pilot | 100 sessions across the existing curator corpus; H4 (active-sampling speedup) + H5 (phone-vs-desktop noise) measured; Crowd-BT Ξ· baseline | All five hypotheses' analysis pipelines green on pilot data | π΅ |
| v1.0 β main | 1500 sessions; H1βH3 evaluated; v48-cond zensim bake decision | The decision rule in docs/STUDY.md Β§3 executes β v48 ships or an honest-stop documenting the next hypothesis |
π΅ |
| v1.1+ | PS-PC predict-fill turned on; BTC boosting for high-fidelity; per-observer Ξ·; mobile-specific (Galaxy Z Fold) sub-study | gated on v1.0 finding | π΅ |
Full study design + decision rules: docs/STUDY.md.
The amplifier table in Β§3 is the authoritative checklist of what's done vs planned. The next-most-impactful tractable chunks:
- End-to-end smoke on a real phone (v0.2 exit gate). The Playwright e2e suite
runs locally (
just e2e; the repo has no CI workflow yet) but a human-on-Galaxy-Fold-7 walkthrough catches everything the headless browser misses. - pwcmp LOO + PΓ©rez-Ortiz 2019 per-observer ACR fits (amplifier #9 deep dive).
The nightly
observer_gradesaggregation already fills the trial-weighted golden pass rate, even-odd consistency, geometric-mean weight, and trusted-pool promotion; the LOO and per-observer ACR fits fill thepwcmp_*andsigma_acr/delta_acrcolumns once we have β₯ 50 sessions per observer. - Tower-mirror cron + db_health β the two remaining data-sprawl v0.2 exit gates.
Open decisions worth surfacing:
- PS-PC at v0.3 or v1.0? The metric ensemble (CVVDP / butteraugli / ssim2 / zensim / PaQ-2-PiQ) isn't yet wired into squintly's pair-selection. v0.3 is the natural slot.
- Anchor source. CID22 is the obvious anchor, but we don't yet have a content-overlap map; need to derive which of CID22's 250 originals overlap our curator corpus (zero, probably β different content pool β in which case we anchor via a controlled stimuli set instead).
- Recruitment. Are we self-recruiting friends + colleagues for the pilot, or going Prolific from day 1? Prolific has the screener depth but anonymous T0 observers should be a viable path on our own site.
- Galaxy Z Fold 7 sub-study. The Playwright device profiles exist; running an actual cohort on foldables would be a separate paper.
Production deploy uses Railway + a 1 GB volume mounted at /data. See
DEPLOY.md for the full walkthrough.
Monitoring: stdout JSON logs (Railway captures them); per-endpoint latency in tracing spans. No third-party telemetry.
- Code: Apache-2.0 OR MIT (dual).
- Per-image content: runs under each source corpus's license (per
src/licensing.rsregistry β 7 policies surfaced on the welcome screen + per-trial badge). Seedocs/motivation-and-compensation.md. - Derived score tables (
pareto.tsv,thresholds.tsv,unified.tsv): planned for CC-BY-4.0 release after study close, conditional on consent posture (seedocs/STUDY.mdΒ§8).
SPEC.mdβ full v0.1 spec (data model, schema, threshold/BT math).docs/STUDY.mdβ formal pre-registration: research questions, hypotheses, design, analysis plan, decision rules.docs/GLOSSARY.mdβ layperson glossary of every term, unit, method, and statistic this README and STUDY.md use, with hyperlinks to all the underlying research papers, datasets, and tools.docs/methodology.mdβ methods reference internal to squintly.docs/participant-grading.mdβ session-grade + weight policy.docs/motivation-and-compensation.mdβ recruitment economics + ethics posture.docs/CORPUS_CURATOR_SPEC.mdβ curator workflow.docs/HANDOFF.mdβ agent / contributor handoff notes.- External: zensim (what we feed), zenanalyze (TSV consumer), coefficient (image store we consume), zenpapers reference book (methodology citations).