slop.space

SlopSpace

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

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.

Hosting

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.

Pipeline

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.

Video

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

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).

Before it goes public

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.