A static page that plays a participant a reel of videos and takes a rating on each, plus a set of filters that build the videos.
site/index.html
site/app.js
site/survey.html
site/anes.js
site/feed.json # bin/feedjson output; what the reel plays
site/videos/<id>.mp4
site/playlists/<participant>.txt # bin/route output; col 1 is the video id
The participant picks a party, watches a fake tDCS device come online, then gets
the reel: clips congruent with the party they picked come first. Veracity is not
in the site. Join on id against stimuli.tsv afterwards.
This is split across two hosts (see Hosting): the party pick is served from
slop.space, and everything after it runs on MindProbe, which records the ratings.
The files above are that second half; site/gate.html is the first.
Two hosts, so the results live on MindProbe while the address stays slop.space.
MindProbe (mindprobe.eu) is a free server running JATOS, which hosts the study
and stores the data; it cannot be embedded in a slop.space iframe (it sends
X-Frame-Options: SAMEORIGIN and a SameSite=Lax session cookie), so the two
are stitched with a plain link, not a frame.
slop.space site/gate.html — pick a party; the check links into the study
MindProbe study/ as a JATOS study — the reel, the ratings, the survey
site/gate.html is the whole of slop.space: the party pick, whose check is an
<a href> into the MindProbe study link carrying the pick as ?data=REP|DEM.
Point the QR code at slop.space. When you’re happy with it, gate.html becomes
index.html.
The study is a JATOS study archive built from study/ — study/index.html and
study/app.js are the reel wired for JATOS; the shared assets are copied in:
cp site/style.css site/feed.json study/
cp site/videos/*.mp4 study/videos/
bin/jzip study
Import SlopSpace.jzip into MindProbe, make a General Multiple study link so
one QR serves everyone, and paste it into site/gate.html in place of
REPLACE_WITH_STUDY_LINK. study/app.js reads the party from
jatos.urlQueryParameters.data, plays congruent clips first, writes every rating
back with jatos.submitResultData, and closes with jatos.endStudy. JATOS
serves jatos.js itself, so it is not in the archive. bin/jzip derives the
study UUID from the name, so re-importing updates the study in place rather than
making a copy.
bin/claims politifact json -> id veracity claim
bin/script + the spoken line -> id veracity claim line
bin/verify drops drifted rows -> same, fewer
bin/outpaint square face -> 9:16 -> portraits/<name>.jpg
bin/video writes raw-videos/<id>.mp4
bin/withoutro settle + the outro -> site/videos/<id>.mp4
bin/feedjson + a lean per claim -> site/feed.json
bin/route + a survey -> id stance reason
bin/claims -n 240 < data/politifact_factcheck_data.json > claims.tsv
bin/script < claims.tsv > scripted.tsv
bin/verify < scripted.tsv > stimuli.tsv
bin/outpaint --faces data/faces -n <rows>
bin/video --faces portraits < stimuli.tsv
bin/withoutro
bin/feedjson < stimuli.tsv > site/feed.json
Per participant: they do survey.html?p=P07, which downloads P07.txt. Put
that in surveys/, then:
bin/route surveys/P07.txt < stimuli.tsv > site/playlists/P07.txt
The survey is 11 ANES items in site/anes.js. The 7-point scales are verbatim
ANES Time Series wording; abortion and immigration are unverified against a codebook.
Sampling, face assignment and trial order are seeded (--seed, default
20260721). Same inputs, same outputs, on any machine.
bin/deploy uploads site/ over FTP and wants FTP_HOST, FTP_USER,
FTP_PASS.
Everything runs under uv via its shebang. Every filter that calls a model
wants GEMINI_API_KEY from AI Studio — a consumer Gemini subscription will not
authenticate a script.
Each clip is one 8s Veo call — the base, no extension. bin/script writes a
single sentence (opener, the claim once, closer) sized to fit. Veo can extend a
clip to hold the same speaker for longer, but a second sentence restating the
claim only read as repetitive, so the reel keeps the first 8s alone.
Veo does not reliably stop on a resting face, so bin/withoutro does not freeze
the last frame — it dissolves the clip into its own first frame, the neutral
portrait it was conditioned on, holds that, then dissolves into the outro. Every
clip ends calm no matter where Veo left the mouth.
The face is first-frame conditioning (source.image). reference_images — which
would let Veo frame freely around a face — is Vertex-only and is rejected on an
AI Studio key, so the first frame’s framing becomes the clip’s framing. A square
SFHQ face cropped to 9:16 zooms into the face; padded, Veo keeps the pad as a
smear. So bin/outpaint first expands each square face into a real 9:16
head-and-shoulders portrait — same identity, with shoulders and a plain
background — and bin/video conditions on that, giving a natural medium shot.
On an AI Studio key only the -preview Veo models exist, and each carries a
small per-project daily cap (undocumented; a few dozen requests) that a day of
generating quickly exhausts, per model. Probe each — a 429 is free, an accepted
request bills.
The way past that cap is Vertex, a separate project and quota pool that also
exposes the GA models (veo-3.1-generate-001, 50 rpm). No code changes: the SDK
routes to Vertex from the environment. Enable billing and the Vertex AI API on a
GCP project, gcloud auth application-default login, then
env GOOGLE_GENAI_USE_VERTEXAI=true GOOGLE_CLOUD_PROJECT=<id> \
GOOGLE_CLOUD_LOCATION=us-central1 \
bin/video --faces portraits --model veo-3.1-generate-001 < stimuli.tsv
A new GCP account’s $300 trial credit covers Veo, which is enough for the whole set several times over. (On Vertex the clip comes back as bytes, on AI Studio as a reference to download; bin/video handles both.)
Named politicians are refused at generation (“we can’t create videos with real people’s names or likenesses”), so claims that name one cannot be used no matter which model is asked.
data/politifact_factcheck_data.json 21152 rows, CC BY 4.0
data/faces/ SFHQ part 1 sample, 1024x1024, CC0
14123 claims survive the verdict filter (8328 false, 5795 true).
Synthetic people stating claims that are sometimes false, with nothing on screen
saying so. robots.txt and noindex are not access control — put it behind a
password before pointing slop.space at it.