MS408 — a cold, reproducible evaluator for Voynich-Manuscript hypotheses
New here? Pick your path.
- You study the Voynich Manuscript (and want to test an idea, or understand what's been found): start with the tutorial, then
LIMITS.mdand the glossary. You do not need to read the code.- You're a developer / not a Voynich specialist: the glossary defines the domain terms; then the API in
ms408.signature,CONTRIBUTING.md, and the reproducibility path (python -m ms408.verify).
What this is. A firewall-disciplined toolkit and methodology for the computational study of the Voynich Manuscript (Beinecke MS 408) and other undeciphered corpora. It does not propose a solution. It gives you a rigorous, reproducible way to evaluate a hypothesis — "is this a cipher of Latin?", "does my generator reproduce the manuscript?", "does this transliteration change the statistics?" — and to grade the answer honestly.
Think of it as a cold logical evaluator: point it at your hypothesis and it reports, against firewall-computed reference bands, which of the manuscript's statistical properties you actually reproduce — and which you only appear to. It is built to be unimpressed, including by its own authors: the project's public record includes the toolkit retracting its authors' own headline result after a concurrently-published cipher was run through it.
What this is not. Not a decipherment, not a translation, not a claim to have solved the
manuscript. No output should be read as meaning. Matching the bands is necessary, not
sufficient: it means a hypothesis is not excluded, never that it is the mechanism. The
statistical evaluator is cold and reproducible; the accompanying adversarial-review
protocol uses a fallible language model and is a discipline, not an oracle
(docs/LIMITS.md, docs/METHODOLOGY.md).
Why it exists
Computational Voynich work has a long record of confident, mutually incompatible
"solutions," because with no ground truth "it looks like X" is nearly unfalsifiable. This
project inverts the order: validate first, claim second. Four coupled disciplines
(detailed in docs/METHODOLOGY.md):
- Harness — every method must separate real language from matched structured-meaningless controls and ciphers before it is trusted on the manuscript.
- Firewall — every number comes from deterministic, versioned code; nothing is
estimated or recalled (this applies to the tool too:
evaluate()refuses to run if its reference-band artifact is missing rather than invent a band). - Evidence grading — every claim carries a grade A–D and never gets upgraded to look stronger.
- Adversarial refutation — every A/B claim is attacked by an independent clean-context
reviewer before it stands; the briefs are archived (
docs/refutations/).
Install
pip install -e . # core evaluator (numpy / pandas / requests only)
pip install -e ".[vision]" # + the optional vision-annotation track (anthropic)
pip install -e ".[dev]" # + pytest / ruff
The tutorial walks through the whole loop end to end.
Quickstart — evaluate your own hypothesis
python -m ms408 my_tokens.txt # whitespace-separated word tokens; prints a table
python -m ms408 --json my_tokens.txt # machine-readable verdict
cat my_tokens.txt | python -m ms408 - # tokens from stdin
my_tokens.txtneeds at least ~1,000 word tokens (the reference bands are built at 10,000; below ~8,000 some axes aren't strictly comparable). Shorter streams are refused with a clear error rather than a misleading verdict — seedocs/LIMITS.md.
from ms408 import evaluate
verdict = evaluate(open("my_cipher_output.txt").read().split())
# Bands are stratified by Currier dialect — there is no pooled "the manuscript" band set.
for dialect, block in verdict["dialects"].items():
print(f"Currier {dialect}: {block['hard_axes_in_band']} of "
f"{block['hard_axes_total']} hard axes match")
for axis, r in block["axes"].items():
flag = " [soft]" if r["soft"] else (" [confounded]" if r["confounded"] else "")
print(f" {axis:12} {r['value']} band={r['band']} in={r['in_band']}{flag}")
# r["caveat"] carries the honest hedge for that axis
print(verdict["best_match"]) # the dialect you matched most closely
# evaluate(tokens, dialect="B") scopes the verdict to one dialect.
Each axis reports its value, that dialect's reference band, and whether you land in it —
with the caveat attached (the homophony-confounded dI, the token-sensitive advisory
ttr and zipf, the soft mid-level syntax z's). The hard_axes count deliberately excludes
the confounded, soft, and advisory axes, so the tool cannot be quoted without its hedges.
Dialect matters more than anything else here. Currier A and B are different generative
regimes: each one's own signature sits outside the other's hard bands on every axis, and B
is 68% of the manuscript. In-band for one dialect is not in-band for the manuscript — always
say which. See docs/LIMITS.md for how well each dialect's bands cover the
rest of that dialect (B's do so poorly on ed1).
Sanity check the discrimination yourself: each dialect lands in all of its own hard bands,
misses the other's, and raw Latin prose (high character entropy, no morphology network)
lands in none of either — tests/test_signature.py pins all three.
Worked example — a real cipher. examples/evaluate_naibbe.py
runs Greshko's Naibbe cipher (2025) through the evaluator. It lands 0 hard axes against both
dialects — and
the example explains why that is not exclusion (the dI collapse is a respacing artifact on
a confounded axis; verbose+homophonic ciphers are inconclusive, not excluded). It is the
sharpest demonstration of the tool's discipline.
Reproduce the numbers. The numbers the tool ships are reproducible from committed code:
python -m ms408.verify # recompute the VMS point + check self-consistency
python -m ms408.verify --full # also rebuild the reference bands and diff vs the shipped file
What's in the box
ms408.evaluate/ms408.signature— the public evaluator:evaluate,axis_values,vms_bands,format_verdict, and the CLI (python -m ms408).ms408.verify— reproduce-our-numbers self-check (python -m ms408.verify [--full]).examples/— runnable demos (evaluate_naibbe.py).ms408.harness,ms408.experiments.e6/e21–e26— matched controls: real-language corpora, self-citation and Naibbe-style cipher generators, positional/reuse/type-lexicon generators.ms408.acquire/ms408.sources— pinned, sha256-verified, license-aware data acquisition.ms408.experiments.e*— the firewall studies behind the papers (reproductions, not the API);e32_reference_bandsbuilds the evaluator's committed reference bands.docs/—TUTORIAL.md(end-to-end walkthrough),METHODOLOGY.md(the refutation protocol),LIMITS.md, the graded synthesis (synthesis/),refutations/(the archived adversary briefs), and the preprints (paper/). Contributing? SeeCONTRIBUTING.md.
The honest record (a feature, not an embarrassment)
docs/refutations/ and the papers document the discipline overturning
the program's own conclusions — a circular positive, a fitted-to-target "sufficiency"
claim, an over-strong negative later walked back, and, most tellingly, a cipher-exclusion
headline retracted after running a concurrently-published cipher (Greshko's Naibbe, 2025)
through this very toolkit. If you use it to test your hypothesis, expect it to be
equally cold with you.
Limits (read before quoting any number)
See docs/LIMITS.md. In brief: the evidence is a single manuscript;
dI is homophony/respacing-confounded (a homophony detector, not a clean word-order
measure); ttr is token-count-sensitive; the two mid-level syntax measures are soft (their
VMS-side confidence interval crosses zero); and the adversarial-review protocol uses a
same-model-family LLM, so cross-vendor or human refutation is the stronger check.
Data & licensing
The package ships no third-party corpora. Run python -m ms408.acquire to fetch pinned,
sha256-checksummed sources into gitignored data/raw/ under a consume-only policy; the
registry (src/ms408/sources.py) records each source's licence. If you use the Naibbe
example data, cite Greshko 2025 (doi:10.1080/01611194.2025.2566408) per its source licence.
Acquired and derived data land under the repo's data/ when you run from a checkout. From a
pip install there is no checkout, so they land in $XDG_DATA_HOME/ms408 (default
~/.local/share/ms408). Set MS408_DATA_HOME to override either case — useful for CI and
shared caches.
Code is licensed under Apache-2.0 (see LICENSE and NOTICE).
Companion preprints: the constraint-envelope paper (paper/v6b/) and the methods paper on
adversarial self-correction (paper/methods/v3/).
Release files for ms408 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ms408-0.2.0.tar.gz | 1.5 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ms408-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.8 MB
Release files / ms408-0.2.0.tar.gz
| Download URL | ms408-0.2.0.tar.gz |
|---|---|
| Size | 1.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c7822fc675b2e295d9b47fa709466a5550aec70ca7f1706ee8adfb21e6b9611c
|
|
BLAKE2b-256 checksum How to use checksums |
5f3414564595cf4ad0ed102ff0a9f10d0a595812344a22e5c11a38424540d35e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / ms408-0.2.0-py3-none-any.whl
| Download URL | ms408-0.2.0-py3-none-any.whl |
|---|---|
| Size | 327.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
086f47dedeea779282a3cb453d51b00752450a4102db657c6958ae2da4fc951b
|
|
BLAKE2b-256 checksum How to use checksums |
9fc9941f40894c66503de9b083aff5ac6fc7ec7567cc4f1a717e6a35efc2b3ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log