Posthumous265 subjects · 910 affinitiesaccuracy 0.198 · chance 0.111reviewed 265

The project's confession · model card

The machine that tried to read them

Every letter is downstream of this model. The full card is reproduced below, unedited, from the same document the repository ships.

Accuracy0.198

Chance0.111

One dot per guess in a hundred, over nine classes.

Model card

The classifier that wrote to 265 strangers, and everything wrong with it.

**Version** v0.3.0, frozen 2026-07-04. Never retrained, never fine-tuned.

Generated metrics: `pipeline/artifacts/model-card-generated.md`.

Bundle evidence: `pipeline/artifacts/model-bundle-manifest.json`.

Summary

A balanced multinomial logistic regression over 70 differential-entropy

features, trained on one EEG dataset and pointed at four others.

--- ---:
Overall accuracy 0.1983
Balanced accuracy 0.1959
Macro F1 0.1884
Expected calibration error 0.0132
Chance, nine classes 0.1111

**It is barely better than guessing.** That is the finding, not a defect to be

engineered away. A project about machine overconfidence needs a machine that is

confidently wrong, and this one is: it will hand you a dominant emotion for a

person who was doing mental arithmetic in 2018, and it will be wrong roughly

four times in five.

Training data

**FACED** — Finer-grained Affective Computing EEG Dataset.

`https://www.synapse.org/Synapse:syn50614194`

Access is account-gated: an authenticated Synapse account, the official

`Data.zip`, and the official `Code.zip`. 123 subjects watched emotional film

clips. Nine classes, in the order the corpus locks:

amusement  inspiration  joy  tenderness
anger  fear  disgust  sadness
neutral

Four positive, four negative, then neutral. Every downstream contract — corpus

schema, web legend, glyph assignment — depends on that order.

**Labels** are the last 30 seconds before each subject-specific `102` event

marker, mapped through the official `video2label()` function extracted from

`Code.zip`. The training entry point refuses to run if that function is not

found in the archive, so the mapping can never silently drift to a

reimplementation.

Emotion labels here are **elicitation labels** — what the stimulus was intended

to provoke. They are not measurements of anyone's inner state, not even in the

training set.

Features

70 values per 4-second epoch: 14 channels × 5 frequency bands.

For a band-filtered Gaussian signal, differential entropy reduces to a

log-variance:

DE = 0.5 * log(2 * pi * e * variance)

Bands, extracted with a 4th-order Butterworth bandpass applied zero-phase:

Band Hz
delta 1 - 4
theta 4 - 8
alpha 8 - 14
beta 14 - 31
gamma 31 - 45

Channels are the 14 that every cleared dataset has in common:

Fp1  Fp2  F3  F4  F7  F8  C3
C4   T7   T8  P3  P4  O1  O2

Feature order is `channel:band`, channel-major. Both training and inference

assert that the computed order matches the order stored in the bundle, so a

montage edit cannot silently permute the feature space.

The estimator

StandardScaler
LogisticRegression(C=1.0, class_weight="balanced",
                   max_iter=1000, random_state=23, solver="lbfgs")

That is the whole model. No deep network, no convolution, no attention. Two

reasons, and the second is the real one:

1. It is a defensible baseline for 70-dimensional tabular features.

2. **It is legible.** The project's argument only holds if the model's behaviour

can be stated in one sentence and audited by someone who did not build it.

Probabilities are the native logistic softmax with no post-hoc calibration.

Validation

Leave-one-subject-out across all 123 FACED subjects. 44,664 labelled epochs,

123 folds.

Splits are **subject-level, never epoch-level**. Epoch-level splitting would put

adjacent 4-second windows from the same person on both sides of the split and

inflate accuracy badly. Every learned transform — scaler and classifier alike —

is fitted inside each fold.

A sensitivity run excluding flagged subject `sub091` moves overall accuracy by

less than 0.0001, so no single subject carries the result.

Confusion matrix and per-class figures: `pipeline/artifacts/model-card-generated.md`.

Architectures considered and rejected

Recorded at the v0.3 design stage:

  • **EEGNet or another neural model.** Rejected. It might have improved eventual

accuracy, but it introduces leakage, calibration and reproducibility risk

before the baseline contract was proven. Higher accuracy was never the goal.

  • **Waiting for SEED or DEAP access before modelling.** Rejected. It would have

produced a stronger cross-dataset story, but it blocks all model work on

external access that is not under repository control.

An SVM-versus-MLP comparison appears in the original v0.1 roadmap as a planned

decision point. **It was never run.** The v0.3 design supersedes it by choosing

the linear baseline and deferring neural models entirely. Earlier documentation

claimed those architectures were "explicitly discarded"; that claim was not

supported by the record and has been removed.

Projection limits

This is the section that matters most, and the one most easily skipped.

The model was validated **only on FACED**. It is then applied to four cohorts

drawn from entirely different studies, doing entirely different things:

Cohort Subjects What they were actually doing
--- ---: ---
Counters 36 mental arithmetic
Imaginers 109 imagined limb movement
Listeners 21 listening to affective music
Lonely 99 auditory oddball task

Only Listeners involves emotion at all, and even there the emotion is a property

of the stimulus rather than a measured state.

**No cross-dataset accuracy has ever been measured.** There is exactly one

validated number in this system — 0.1983, internal to FACED. Every projection

output is an artistic projection, not a measurement, and must be read as one.

Fraction of each cohort whose mean confidence falls below 0.35, from

`pipeline/artifacts/domain-diagnostics.json`:

Cohort Low-confidence fraction
--- ---:
Counters 93.1%
Imaginers 97.4%
Listeners 74.0%
Lonely 90.9%

Listeners is the least out-of-distribution cohort, which is consistent with it

being the only one where anyone was shown emotional stimuli.

Across the whole 265-subject corpus, mean confidence runs from 0.196 to 0.826

with a median of 0.255, and **245 of 265 subjects sit below 0.35**. The model

rarely commits to anything. Downstream prose must hedge every emotional

assertion, and the archive's visual language is built to show this rather than

hide it.

Per-feature drift and out-of-distribution scores are available through

`pipeline.model.domain_shift` — two-sample KS tests per feature plus a

standardised mean-shift score against the FACED reference.

Class order reconciliation

The frozen bundle stores `class_order` alphabetically, because that is what the

underlying scikit-learn estimator produces. The corpus contract fixes the

locked order shown above.

The remap happens in `pipeline/inference/predictions.py`: epoch-level softmax

probabilities are summed per emotion and re-emitted in the locked order before

the corpus distribution is computed. The bundle's alphabetical ordering is an

internal detail that never reaches the corpus or the website.

Provenance and redistribution

The trained bundle, its deterministic outputs, and the FACED preprocessing

pipeline are **local-only artifacts**. The bundle is derived from account-gated

data and is not redistributed; `pipeline/model_bundle/` is gitignored.

`pipeline/artifacts/model-bundle-manifest.json` is committed and records the

bundle's SHA-256, feature count, class order, dependency versions and metrics.

Inference verifies the local bundle against that manifest before loading it, so

a bundle that does not match the committed evidence cannot silently produce

predictions.

No waveform data is redistributed anywhere in this project.

Intended and unintended use

**Intended.** An artwork about machine overconfidence, and a demonstration of

what happens when a weak classifier is applied outside its training

distribution with full disclosure.

**Not intended, and not supported by anything here.** Diagnosis. Screening.

Assessment of any individual. Any claim about what a participant actually felt.

Any deployment where a person is affected by the output.

Participants did not consent to this and cannot answer it. Corrections and

withdrawal: `hello@aadrikas.space`.

Edge policy

How the archive decides that two strangers are kin.

**Decided** 2026-07-04, at the v0.4 → v0.5 hand-off. Unchanged since.

Implemented in `pipeline/corpus/edges.py`; rendered on `/the-model`.

What an edge is

Nothing about these people is compared directly. The only thing the archive

knows about a participant is the nine-dimensional probability distribution the

classifier produced for them, so an edge compares *those* — one guess against

another guess.

An edge therefore asserts: **the model was confused about these two people in

the same way.** It is not a claim that they are alike.

The rules

  • **Threshold.** Cosine similarity ≥ 0.85 between two subjects' nine-dimensional

prediction distributions. Below that, no edge exists.

  • **Ceiling.** Each subject nominates at most its five strongest

threshold-clearing candidates. The persisted edge set is the undirected union

of those nominations; the rendered kin list is capped at five.

  • **No quota filling.** A subject with fewer than five candidates above the

threshold keeps the honest shorter list. No weaker edge is ever invented to

round the number up. One participant — `lonely/sub-37` — has no edges at all,

and the archive says so rather than manufacturing one.

  • **Ordering.** By similarity descending, then by global corpus ID ascending, so

the output is deterministic.

  • **Edge identity.** `edge_id = "{low_id}--{high_id}"` over the lexically sorted

endpoint corpus IDs. Qualified IDs are required because raw subject IDs

collide across cohorts, and sorted IDs keep the corpus JSON diffable.

  • **No self-edges.** The assembler drops them with a warning.
  • **Stable skip.** Subjects whose distribution is all-zero or perfectly uniform

are excluded from the candidate pool with a warning, so their low

`mean_confidence` is preserved rather than laundered into a false edge.

  • **Cohort tie-break.** When two candidates tie on similarity and ordering, the

cross-cohort edge wins. This only affects exact ties; ordinary sorting already

produces far more cross-cohort edges than within-cohort ones.

What this produces

--- ---:
Edges 910
Nodes with at least one edge 264 of 265
Similarity range 0.850 – 0.998
Cross-cohort share 43%
Mean degree 6.9

The caveat that matters

Cosine similarity on a nine-simplex where `neutral` and `tenderness` are the

dominant prediction for 153 of 265 subjects produces a graph that is **close to

a clique in disguise**. The 0.85 threshold is the only thing holding it at 910

edges rather than tens of thousands.

Anything built on this graph — layout, clustering, community detection — must be

validated against that fact rather than assuming the structure is meaningful.

The web layer's spatial layout does exactly this: it runs a force-directed

embedding first, measures whether the result collapsed, and falls back to a

stress-majorised embedding when it has. See `web/lib/layout.ts`.

Out of scope

  • A manual "kin of N" override at the UI level. Reserved for the opt-out

workflow.

  • Revealing projection-cohort edges without the domain limits alongside them.

The limits are published in [`model-card.md`](model-card.md); the edges are

never shown without them.