Printed EER for cosine of direction plus speed, using five references per signer on 94 available SUSIG users, 100 Hz ePad-ink capture, 15 genuine tests and 10 skilled forgeries per signer.
The specimen is yours · pattern
The Signature You Cannot Repeat
Draw the same invented mark twice. The browser keeps the full path, timing and stroke boundaries on this page, then asks a narrow question: how do these two captured performances differ under one displayed dynamic time warping rule? It does not decide whether they match, who drew them, or whether either is genuine.
Do not draw your legal, banking or usual signature. Invent a mark that means nothing. A dynamic trace is sensitive behavioural data even when this page keeps it local.
Published anchor first
Five rows from a 2009 paper
Khalil, Moustafa and Abbas printed a symmetric cost matrix for five signatures. This block starts from only the ten upper-triangle values, mirrors them, removes each diagonal zero, and recovers the nearest and farthest cost in every row. Nothing from your drawing is compared with these costs. Different hardware, features and protocols make that comparison meaningless.
Rebuilding the published example.
Now bring the unrepeatable thing
The shipped synthetic mark is already running below, so every part of the instrument has something to say before you touch it. Draw over either panel to replace that repetition. Add another stroke by lifting and drawing again.
Synthetic fallback loaded: 93 samples, three traces, CC0 1.0.
Time runs from cyan to amber. Each lift begins a new stroke.
Sampling follows Pointer Events and any coalesced events the browser exposes.
This is a measurement under the controls below. There is no threshold and no identity verdict.
Open the feature and normalization controls
Only direction, speed and pressure read the drawn data. The stroke-start marker is structural: if direction, speed and pressure are all zero, two traces whose stroke layouts already agree score exactly zero no matter what was drawn. The page forbids that whole family of settings, not just the all-zero one, and restores direction weight to 1. This rule was widened after a review found the stroke-start-only case, and the visible check panel now measures the vacuity it prevents. The page also forces pressure weight to zero unless both traces contain genuinely supported pressure samples.
Keep the path. Change the clock.
A common dismissal is that this machinery only rewards the same outline. So the page makes a controlled counterfactual from repetition A. It retains the normalized arc-length path and stroke boundaries exactly, then spreads each stroke's original duration uniformly along that path. This is not a forgery and not a model of another person. It is a probe with one controlled difference.
Layer two
Three synchronized comparisons
The first three cells fix their own weights, so they name every contributor. The fourth uses the whole selected weight set, currently direction 1, speed 1, stroke start 4, pressure 0, and the stroke-start term is part of it. Read the first two cells knowing what they cost: against the uniform-time probe the path and direction cells are zero by arithmetic rather than by measurement, because the probe copies the processed coordinates and direction is computed from them. Speed is the only channel that can differ from this probe, which is why the check panel below also runs a reparameterised twin, whose zero is measured, and why a zero speed weight makes the fourth cell say nothing at all.
What entered the calculation
Raw samples and transforms
| trace | index | x CSS px | y CSS px | t ms | stroke | pointer | pressure | supported |
|---|
The table shows the first 240 raw samples. The count above is the full count. Export preserves every sample, not only the visible rows.
The check
Running independent failure cases.
Every uncertainty and free choice
- Sampling: Pointer Events plus getCoalescedEvents() when present. Browser, device, operating system, display load and input type change the samples.
- Time: each event batch is mapped onto the current performance.now() clock while retaining coalesced-event deltas. Duplicate times remain; every derivative uses a displayed 1 ms floor.
- Space: CSS pixels, not tablet units. Translation can use the path-length-weighted centroid, first point or nothing. Isotropic scale uses the bounding-box diagonal. Rotation is optional and off.
- Resampling: each stroke is resampled separately at equal arc length, with 128 points total by default and at least two per stroke. The two-per-stroke floor wins, so a trace with more strokes than half the requested count ends above that count; the transform ledger prints the requested and allocated totals for both traces. Pen-up travel is excluded.
- Size limits, and one rule changed after seeing data: a file may not exceed 2,000,000 bytes or 20,000 samples per trace. A review specimen inside both limits, 10,000 two-point strokes per trace, would have been resampled to 20,000 points and asked for two 400,000,000-cell matrices, about 6.4 GB. The parser now also refuses more than 512 strokes in a trace, which holds the warping matrix at or under 1024 by 1024 cells. That limit was added after seeing that specimen.
- Smoothing: optional five-sample Savitzky-Golay on x and y with coefficients [-3, 12, 17, 12, -3] / 35. The first and last two points of each stroke are unchanged.
- Derivatives: direction is [dx/r, dy/r]. Speed is r / max(dt, 1 ms). Acceleration is the speed difference over the same floored time and is too jitter-sensitive for the default score.
- Standardization: speed and pressure use one pooled median and median absolute deviation for the pair, with epsilon 0.000001. Pressure defaults to weight zero. The page forces that weight to zero unless both traces contain genuinely supported pen-pressure samples.
- When the epsilon becomes the divisor: if both marks are drawn at an almost perfectly steady rate, the pooled speed spread falls below that epsilon and the standardized speed term is divided by the epsilon rather than by the data. The score can then reach the millions while the two performances are in fact very close. The page detects this case and prints the caution beside the score instead of letting a large number speak for itself.
- Local cost: wDir*((sinA-sinB)^2+(cosA-cosB)^2) + wSpeed*(vA-vB)^2 + wLift*(bA-bB)^2 + wPressure*(pA-pB)^2.
- Warping: C[i,j] = d(i,j) + min(C[i-1,j], C[i,j-1], C[i-1,j-1]), starting at the top left and ending at the bottom right. The default has no Sakoe-Chiba window. The window is not decoration: on the shipped pair the score moves from 0.421029 unwindowed to 0.430038 at 8, 0.476719 at 4 and 0.633974 at 1. It cannot change the counterfactual, because both sides of that comparison are resampled at equal arc length and end up the same length, so the diagonal alignment is always available at zero cost. A comparison run on raw captured samples rather than arc-length resampled ones does lose its timing blindness as the band narrows, which is one more reason the resampling step is named here rather than buried.
- Stroke constraint: different stroke indices have infinite local cost unless the reader disables the constraint. Different stroke counts can therefore produce no finite path, which the page states instead of printing a score.
- Reported score: accumulated cost divided by recovered path length. It is a weighted-feature score: every nonzero weight, including the stroke-start weight of 4, is inside the headline number, which is why the headline names the weights instead of naming two features. Settings where no data-varying feature carries weight are forbidden.
- A second rule changed after seeing data: the weight guard above asks whether a feature reads the drawing at all. A reconcile pass measured that this is the wrong question for the counterfactual column, because the uniform-time probe copies direction, stroke start and pressure from its source, leaving speed as the only channel that can differ from it. Every one of the sixty reachable weight settings with a zero speed weight therefore scored exactly zero against that probe for every mark tested, while the guard reported no adjustment. The page now measures the condition directly, by asking whether every channel that carries weight is pointwise identical across the pair, and says so beside the score instead of letting a forced zero read as agreement. The guard is not widened to change the reader's weights here, because against captured repetition B a zero speed weight is a perfectly informative setting; only against the copied probe is it empty.
- Privacy audit: the counter covers page-level calls to fetch, XHR open and send, WebSocket, EventSource, beacon, form submission and service-worker registration, plus Resource Timing entries after load. It cannot audit extensions, the browser, the operating system or the host.
The trace stays on this side of the glass
The page has no analytics, remote font, third-party script, upload endpoint or persistence call. It does not use localStorage, IndexedDB, cookies, logs or any navigation, query or network URL for traces. The one exception is deliberate: the export button serialises your two traces into a blob: URL that exists only inside this tab, hands it to a download link and revokes it a second later. Its Content Security Policy says connect-src 'none'. The counters above make attempted sending visible, and the browser's Network panel is the independent place to check. Resource requests needed to load this page can appear there. No request body should contain a trace.
The traces themselves are held in this tab's memory for as long as the instrument is open, because a comparison cannot be computed without them. That is retention in memory, not storage: nothing is written to disk unless you press export, and nothing is sent at all. Clearing memory, replacing a repetition or closing the tab ends it.
The claim has a boundary. Page code can show what page code attempted. It cannot prove that a browser extension, browser vendor, operating system, input driver or hosting environment is incapable of observing input. Clear memory drops the arrays and overwrites mutable live samples where JavaScript permits. It cannot command the JavaScript engine to erase old copies immediately.
Published performance lives on another scale
An equal error rate, EER, is the operating point, usually interpolated, where false acceptance and false rejection rates are equal under one named dataset and protocol. It is not the chance that this drawing is genuine, the error probability of one decision, or necessarily a deployed threshold.
Best average skilled-forgery EERs on the hidden 60-user evaluation for Tasks 1 and 2. Standard deviations were 5.64% and 5.69%. Both maximum per-user values were 30.00%.
Best Table 6 line: false rejection rate and random-forgery false acceptance rate. They are distinct operating measures, not an EER, across 1,232 signatures from 102 writers with three-reference templates.
SVC2004 used five enrollment signatures, ten trials, 10 genuine probes, 20 skilled forgeries and 20 random forgeries per user. Task 2 added orientation and pressure, yet its best average skilled-forgery EER was slightly higher than Task 1. That result does not support a general claim that more tablet channels necessarily help. None of these percentages transfers to this browser, device, feature rule or pair of drawings.
What this does not prove
It does not prove that either drawing is genuine, that both came from one person, that dynamic handwriting is unique, that a copied shape would fail a real verifier, or that any published EER applies here. There is no enrollment population, impostor distribution, skilled-forgery set, learned threshold or error estimate. The live result is a local measurement. The same-path replay proves only that this chosen dynamic representation can contain timing information absent from the rendered outline.