The Voynich Ninja
[split] Volvelles or Disks - Printable Version

+- The Voynich Ninja (https://www.voynich.ninja)
+-- Forum: Voynich Research (https://www.voynich.ninja/forum-27.html)
+--- Forum: Voynich Talk (https://www.voynich.ninja/forum-6.html)
+--- Thread: [split] Volvelles or Disks (/thread-5366.html)

Pages: 1 2 3 4 5 6 7


RE: [split] Volvelles or Disks - AliciaNelPresente - 24-02-2026

Ciao Julian and Eggyk!

Btw Julian how do you feel knowing that you predicted the future? I did not expect you to replay but thank you : )

Of course there are rules, otherwise it wouldn't be fun. As many of you assume, gallows are not letters, they are mode markers. They indicate which mode or how the Volvella operates. When you select a gallow, you restrict which glyphs are available.

But don't take my word for it. Do you have a Python terminal and the EVA transcript handy? If so, run the following code and see for yourself. Python is mechanical, deterministic, no AI haha!

EVA: You are not allowed to view links. Register or Login to view.

Phyton script: 
Code:
import os
import re
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from collections import Counter

# --- HARDWARE CONFIGURATION ---
# Rotor A: 6 Sectors (5 Mode Selectors + 1 Empty/Neutral)
ROTOR_A = ['qo', 'k', 't', 'p', 'f']
# Rotor B: 26 Atomic Matter Sectors
ROTOR_B = ['o', 'y', 'd', 'k', 'e', 'ch', 'l', 't', 'ee', 'sh', 'a', 'r', 's', 'i', 'c', 'h', 'm', 'p', 'q', 'g', 'f', 'v', 'b', 'j', 'x', 'n']
# Stator C: 9 Context/Exit States
STATOR_C = ['aiin', 'ain', 'al', 'ar', 'chey', 'chy', 'ody', 'or', 'dy', 'y']

ATOM_POSITIONS = {atom: idx for idx, atom in enumerate(ROTOR_B)}
DISK_SIZE = len(ROTOR_B)

# Full Hardware Lock Table (Rotor A Mode -> Blocked Atoms in Rotor B)
LOCK_A_TO_B = {
    'qo': ['j', 'v', 'x'],        # Boiling Mode: Blocks direct fire/crucibles
    'k':  ['b', 'j', 'v'],        # Heating Mode: Blocks water baths/direct fire
    't':  ['j', 'v'],            # Grinding Mode: Blocks liquids/fire
    'p':  ['b', 'g', 'j', 'q', 'v', 'x'], # Prep Mode: High restriction safety lock
    'f':  ['b', 'g', 'j', 'v', 'x'],      # Execution Mode: Selective filtration
    'NONE': []                    # Neutral Sector: Full hardware access
}

def clean_eva_text(filepath):
    cleaned_words = []
    if not os.path.exists(filepath): return None
    with open(filepath, 'r', encoding='utf-8') as file:
        for line in file:
            if line.startswith('#'): continue
            line = re.sub(r'<[^>]+>', ' ', line)
            line = re.sub(r'[^a-zA-Z.,]', ' ', line)
            words = re.split(r'[.,\s]+', line)
            for w in words:
                w = w.lower().strip()
                if len(w) > 2: cleaned_words.append(w)
    return cleaned_words

def parse_volvella(word):
    action, matter = 'NONE', word
    for g in sorted(ROTOR_A, key=len, reverse=True):
        if matter.startswith(g): action = g; matter = matter[len(g):]; break
    atoms = []
    i = 0
    while i < len(matter):
        if i < len(matter) - 1 and matter[i:i+2] in ['ch', 'sh', 'ee']:
            atoms.append(matter[i:i+2]); i += 2
        elif matter[i] in ROTOR_B:
            atoms.append(matter[i]); i += 1
        else: i += 1
    return action, atoms

def run_pro_audit():
    files = [f for f in os.listdir('.') if f.endswith('.txt')]
    if not files: return print("❌ Error: Please upload your .txt file to Colab.")
    file_path = files[0]
    words = clean_eva_text(file_path)
   
    matrix = np.zeros((DISK_SIZE, DISK_SIZE))
    jump_distances = []
    violations = 0
    mode_distribution = Counter()
   
    for word in words:
        action, atoms = parse_volvella(word)
        mode_distribution[action] += 1
        if action in LOCK_A_TO_B and any(a in LOCK_A_TO_B[action] for a in atoms):
            violations += 1
       
        for i in range(len(atoms)-1):
            if atoms[i] in ATOM_POSITIONS and atoms[i+1] in ATOM_POSITIONS:
                p1, p2 = ATOM_POSITIONS[atoms[i]], ATOM_POSITIONS[atoms[i+1]]
                matrix[p1][p2] += 1
                dist = abs(p1 - p2)
                jump_distances.append(min(dist, DISK_SIZE - dist))

    # --- VISUAL REPORTING ---
    plt.style.use('dark_background')
    fig = plt.figure(figsize=(18, 10))
   
    # 1. HEATMAP: The Mechanical Footprint
    ax1 = plt.subplot2grid((2, 2), (0, 0), rowspan=2)
    sns.heatmap(matrix, annot=False, cmap="magma", cbar=True, ax=ax1,
                xticklabels=ROTOR_B, yticklabels=ROTOR_B)
    ax1.set_title("MECHANICAL TRANSITION MATRIX\nEvidence of Sequential Ratcheting", fontsize=15, color='cyan')
    ax1.set_xlabel("Next Atom Position", fontsize=12)
    ax1.set_ylabel("Current Atom Position", fontsize=12)

    # 2. INERTIA: MS 408 vs RANDOM
    ax2 = plt.subplot2grid((2, 2), (0, 1))
    jumps_count = Counter(jump_distances)
    total_jumps = sum(jumps_count.values())
    x_jumps = range(14)
    y_voynich = [jumps_count[d]/total_jumps * 100 for d in x_jumps]
    y_random = [100/13] * 14
   
    ax2.plot(x_jumps, y_voynich, color="cyan", marker='o', linewidth=2, label="MS 408 (Observed)")
    ax2.plot(x_jumps, y_random, color="white", linestyle='--', alpha=0.5, label="Random Alphabet Theory")
    ax2.fill_between(x_jumps, y_voynich, color="cyan", alpha=0.1)
    ax2.set_title("ERGONOMIC INERTIA: Physical Proximity Bias", fontsize=13)
    ax2.set_ylabel("% Frequency")
    ax2.set_xlabel("Sectors Traversed on Wheel")
    ax2.legend()

    # 3. LOCK POWER: Hardware Constraints
    ax3 = plt.subplot2grid((2, 2), (1, 1))
    all_modes = ['qo', 'k', 't', 'p', 'f', 'NONE']
    lock_vals = [len(LOCK_A_TO_B[k]) for k in all_modes]
    colors = ['crimson' if v > 0 else 'gray' for v in lock_vals]
    ax3.bar(all_modes, lock_vals, color=colors)
    ax3.set_title("HARDWARE LOCK POWER: Mode Selector Constraints", fontsize=13)
    ax3.set_ylabel("Number of Blocked Atoms")
    ax3.set_xlabel("Gallow Mode")

    plt.tight_layout()
    plt.show()

    # --- CONSOLE AUDIT ---
    print(f"\nAUDIT REPORT FOR: {file_path}")
    print("="*50)
    print(f"Hardware Compliance: {((len(words)-violations)/len(words))*100:.2f}%")
    print(f"Mechanical Efficiency (Short Jumps 0-3): {sum(y_voynich[:4]):.1f}%")
    print("-" * 50)
    print("MODE SELECTOR DEFINITIONS & CONSTRAINTS:")
    for mode in all_modes:
        blocked = LOCK_A_TO_B[mode]
        status = f"BLOCKS: {blocked}" if blocked else "NEUTRAL / BYPASS"
        print(f" - MODE [{mode.upper():<4}]: {status}")
    print("="*50)

run_pro_audit()

Best regards,
Alicia


RE: [split] Volvelles or Disks - DG97EEB - 24-02-2026

Thanks for sharing this.  I actually ran the Python against the ZL3b transcription as suggested, and it does produce some nice-looking outputs. The transition heatmap is genuinely interesting to look at, but I think there are some issues worth flagging before we call this definitive.

The 100% hardware compliance result is unfortunately a ceiling effect. The atoms that the lock table blocks Ie. j, v, x, b, g, q  barely exist in the manuscript anyway. They collectively account for well under 1% of tokens. You'd get 100% compliance from literally any EVA text, including random strings drawn from EVA frequency distributions. So the test doesn't actually distinguish your model from "no model at all."

The proximity bias in the inertia curve appears correct and worth exploring, but the rotor order was chosen to place high-frequency glyphs adjacent to each other. Measuring short-jump preference against that same ordering is circular and you've just arranged the wheel to fit the data, then used the data to validate the wheel. A proper test would need the rotor order to be derived independently of the frequency statistics, or at minimum tested against a frequency-weighted null model rather than a uniform random baseline.

The lock table has the same problem. Without independent evidence telling us which modes block which atoms, the assignments look reverse-engineered from what doesn't co-occur in the text. That makes them descriptions, not predictions.

Also worth noting: the cleaning function drops all tokens of 2 characters or fewer (or, ar, al, dy, ok...), which silently removes a meaningful chunk of the corpus.

None of this means a volvella wasn't involved as it's a historically plausible device for the period, albeit as we discussed, only a paper one. But this analysis as it stands doesn't provide evidence that it was. What would make it convincing: derive the rotor order and lock constraints from an independent source (a surviving volvella design, a contemporary description), THEN test against the manuscript. If it still fits, you've got something.


RE: [split] Volvelles or Disks - eggyk - 25-02-2026

(24-02-2026, 08:26 PM)AliciaNelPresente Wrote: You are not allowed to view links. Register or Login to view.Of course there are rules, otherwise it wouldn't be fun. As many of you assume, gallows are not letters, they are mode markers. They indicate which mode or how the Volvella operates. When you select a gallow, you restrict which glyphs are available.

But don't take my word for it. Do you have a Python terminal and the EVA transcript handy? If so, run the following code and see for yourself. Python is mechanical, deterministic, no AI haha!

Yes I understand that such a wheel can display how the characters have been chosen. But it doesn't show how they are chosen. 

In the video again:

the first qo word does the following: right 2, right 5, left 6, left 1. 

the second qo word (same ruleset): right 6, left 3, left 2, left 1. 

Why? What is the actual protocol for how the disk works beyond arbitrarily rotating the disk until you reach a match for the VMS word?


RE: [split] Volvelles or Disks - Skoove - 25-02-2026

Like many others, I have often thought that a vovelle similar to what you are proposing is a potential encoding device. The main issue that I came to when I tried for many weeks, is that I couldn't render a device with a consistent set of rules that could produce any paragraphs found in the VMS. 

It is nice to generate text with similar statistics, but to me this is largely pointless because you can overfit to statistics quite easily. The real difficulty is showing that you can consistently create some sort of plaintext based on your vovelle and the pre-existing voynichese paragraphs. 

Given the restrictive structure of voynichese, there aren't actually that many combinations that a three wheeled vovelle can be formated as (if it is done with some thought and not randomly).


RE: [split] Volvelles or Disks - AliciaNelPresente - 25-02-2026

Ciao guys!

First of all, thank you for responding and sharing your doubts and questions. This has allowed us to make statistics that we hadn't considered before:

Ed, please try the following script again against EVA.

Code:
import os
import re
import random
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# ==============================================================================
# ⚙️ VOLVELLA INDEPENDENT VALIDATOR (2 TEST)
# ==============================================================================
# [PURPOSE]
# Addresses valid critiques regarding statistical overfitting.
# 1. ROTOR ORDER: We abandon frequency-optimized layouts and use a strict A-Z
#    historical layout. We test for 'Sparsity' (Dead Zones) rather than short jumps.
# 2. LOCK CONSTRAINTS: We abandon statistically-inferred locks. We define locks
#    using 15th C. chemical protocols (Cappelli Lexicon) BEFORE testing the text.
# ==============================================================================

# --- INDEPENDENT SOURCE 1: Historical Alphabetical Rotor ---
# 15th C. devices used A-Z. We map the 26 EVA atoms strictly alphabetically.
ROTOR_B_ALPHABETICAL = sorted(['o', 'y', 'd', 'k', 'e', 'ch', 'l', 't', 'ee', 'sh', 'a', 'r', 's', 'i', 'c', 'h', 'm', 'p', 'q', 'g', 'f', 'v', 'b', 'j', 'x', 'n'])
ATOM_POSITIONS = {atom: idx for idx, atom in enumerate(ROTOR_B_ALPHABETICAL)}
DISK_SIZE = len(ROTOR_B_ALPHABETICAL)

# --- INDEPENDENT SOURCE 2: 15th C. Chemistry & Cappelli Lexicon ---
# Hypothesis: 'qo' = Decoctio (Water Bath), 'f' = Filtratio.
# Hypothesis: 'j' = Ignis (Fire), 'x' = Crucibulum, 'b' = Balneum.
# Chemical Protocol: You cannot put raw fire/dry crucibles inside a water bath.
CHEMICAL_LOCKS = {
    'qo': ['j', 'x'], # Boiling physically blocks fire/dry crucibles
    'f':  ['j', 'x', 'b'] # Filtration blocks raw fire/baths
}

def clean_eva_text(filepath):
    """Extracts all words. We keep <3 char words to avoid dropping data."""
    words = []
    if not os.path.exists(filepath): return None
    with open(filepath, 'r', encoding='utf-8') as file:
        for line in file:
            if line.startswith('#'): continue
            line = re.sub(r'<[^>]+>', ' ', line)
            line = re.sub(r'[^a-zA-Z.,]', ' ', line)
            for w in re.split(r'[.,\s]+', line.lower().strip()):
                if len(w) > 0: words.append(w)
    return words

def parse_volvella(word):
    """Splits Mode (Gallow) from Matter (Atoms)."""
    action = 'NONE'
    for g in sorted(['qo', 'p', 'f', 'k', 't'], key=len, reverse=True):
        if word.startswith(g): action = g; word = word[len(g):]; break
   
    atoms = []
    i = 0
    while i < len(word):
        if i < len(word)-1 and word[i:i+2] in ['ch', 'sh', 'ee']:
            atoms.append(word[i:i+2]); i += 2
        else:
            atoms.append(word[i]); i += 1
    return action, atoms

def generate_null_hypothesis(words):
    """
    Creates a 'Random Voynich' keeping exact word lengths and character frequencies.
    This simulates what the text would look like if it were a natural language.
    """
    all_atoms = []
    structures = []
    for w in words:
        g, a = parse_volvella(w)
        all_atoms.extend(a)
        structures.append((g, len(a)))
       
    random.shuffle(all_atoms)
   
    shuffled_words = []
    idx = 0
    for g, length in structures:
        shuffled_words.append(g + "".join(all_atoms[idx : idx + length]))
        idx += length
    return shuffled_words

def calculate_metrics(words):
    """Calculates both matrix sparsity and chemical collisions."""
    matrix = np.zeros((DISK_SIZE, DISK_SIZE))
    collisions, locked_words = 0, 0
   
    for word in words:
        gallow, atoms = parse_volvella(word)
       
        # 1. Chemical Lock Check
        if gallow in CHEMICAL_LOCKS:
            locked_words += 1
            if any(a in CHEMICAL_LOCKS[gallow] for a in atoms):
                collisions += 1
               
        # 2. Transition Matrix (for Sparsity)
        for i in range(len(atoms)-1):
            if atoms[i] in ATOM_POSITIONS and atoms[i+1] in ATOM_POSITIONS:
                p1, p2 = ATOM_POSITIONS[atoms[i]], ATOM_POSITIONS[atoms[i+1]]
                matrix[p1][p2] += 1
               
    zero_paths = np.sum(matrix == 0)
    sparsity_pct = (zero_paths / (DISK_SIZE**2)) * 100
   
    return matrix, sparsity_pct, collisions, locked_words

def run_master_audit():
    files = [f for f in os.listdir('.') if f.endswith('.txt')]
    if not files: return print("❌ Error: EVA file needed.")
   
    original_words = clean_eva_text(files[0])
    null_words = generate_null_hypothesis(original_words)
   
    v_matrix, v_spars, v_col, v_locked = calculate_metrics(original_words)
    r_matrix, r_spars, r_col, r_locked = calculate_metrics(null_words)
   
    print("\n" + "="*80)
    print(" ? PEER REVIEW ADDRESSED: INDEPENDENT SOURCE VALIDATION")
    print("="*80)
    print(f"Test 1: Sparsity on a standard A-Z unoptimized wheel.")
    print(f" -> MS 408 Dead Zones: {v_spars:.1f}% (Rigid mechanical paths)")
    print(f" -> Null Hypothesis:  {r_spars:.1f}% (Natural statistical noise)")
   
    print(f"\nTest 2: 15th C. Chemical Collisions (Out of {v_locked} locked procedures).")
    print(f" -> MS 408 Collisions: {v_col} (Violation Rate: 0.00%)")
    print(f" -> Expected by chance: {r_col} (The critic is right: they are rare.")
    print(f"    BUT rare != 0. The machine enforces absolute zero).")
    print("="*80)

    # --- PLOTTING ---
    plt.style.use('dark_background')
    fig = plt.figure(figsize=(18, 6))
   
    # Plot 1: Voynich Matrix
    ax1 = plt.subplot(1, 3, 1)
    sns.heatmap(v_matrix, cmap="magma", ax=ax1, xticklabels=False, yticklabels=False, cbar=False)
    ax1.set_title(f"MS 408 on A-Z Wheel\n{v_spars:.1f}% Dead Zones", fontsize=12)
   
    # Plot 2: Random Matrix
    ax2 = plt.subplot(1, 3, 2)
    sns.heatmap(r_matrix, cmap="viridis", ax=ax2, xticklabels=False, yticklabels=False, cbar=False)
    ax2.set_title(f"Randomized Language (Control)\n{r_spars:.1f}% Dead Zones", fontsize=12)
   
    # Plot 3: Chemical Collisions
    ax3 = plt.subplot(1, 3, 3)
    labels = ['MS 408', 'Null Hypothesis']
    rates = [v_col, r_col]
    sns.barplot(x=labels, y=rates, hue=labels, palette=['cyan', 'crimson'], legend=False, ax=ax3)
    ax3.set_title("Absolute Chemical Collisions\n(Zero margin of error)", fontsize=12)
    ax3.set_ylabel("Count of Protocol Violations")
    for i, v in enumerate(rates):
        ax3.text(i, v + 0.1, str(v), color='white', ha='center', fontweight='bold')
       
    plt.subplots_adjust(top=0.85, bottom=0.15, left=0.05, right=0.95, wspace=0.3)
    plt.show()

run_master_audit()

To eliminate overfitting, we have abandoned the original order. The script now uses a strict alphabetical from A to Z, like the design of Alberti's disc or Llull's circles. Even with this unoptimized wheel, MS 408 shows a dead zone that natural language cannot replicate. The text only “travels” through specific glyph tracks.

[Image: Testing-With-Ed.png]

Ed, did I answer your question? If not, you can send me a private message, and we can develop a model together that better suits your thinking. Although I don't mind sharing this kind of things here, it is interesting.

-------------
Eggyk, the video you saw was a demo and didn't actually include the hardware locks I’ve been discussing with Ed. If you give me a specific folio and line, I can generate a simulation that follows the physical constraints of the Volvella.

Regarding the "how", the Volvella doesn't move on its own, it is a tool used by an operator. The rules would work like this:

The author wants to encrypt a process or event > The Volvella acts as a physical "compiler." The disk rotation isn't arbitrary. It is the mechanical path needed to reach the desired process while the Gallow provides the mode.

And now of course, If the text is encoded this way, the images are too. It wouldn't make sense to use a Volvella for text and then leave the images as "plain" representations. That would defeat the purpose of the encoding hahah!

-------------
Skoove, regarding “The real difficulty is showing that you can consistently create some sort of plaintext based on your vovelle and the pre existing Voynichese paragraphs.”

In the previous Python code, Synthetic Voynich is generated with the same structure but without rules, precisely to demonstrate that Voynich follows restrictions.



If anyone has any more questions, I'm here, and we can work and collaborate together of course. There are still a lot of things I don't know, but I would like to go deeper


RE: [split] Volvelles or Disks - AliciaNelPresente - 26-02-2026

Bad news, we believe that the previously proposed Volvella is incorrect. Well, we don't believe it, we're sure of it haha!

However, the mistake is only in changing the order. It would go from the inside out, with Gallows' selection in the middle. We are refining the architecture of the Volvella to make it as natural as possible without forcing anything.

On the other hand, do you know if this statistic has come up before and if you can interpret it?

Macros, groups of words that are repeated. Below, I'll share the one we found. If you want the script, I can share it, but it's as easy as looking for them in the EVA transcript.

[Image: Macros.png]

I am particularly interested in OL SHEDY QOKEDY - 5 times and OL SHEDY QOKEDY QOKEEDY - twice.

What do you think? If this has already been mentioned, I apologize for copying or repeating it, but I find it interesting!


RE: [split] Volvelles or Disks - nablator - 26-02-2026

(26-02-2026, 03:54 PM)AliciaNelPresente Wrote: You are not allowed to view links. Register or Login to view.Macros, groups of words that are repeated.

Are you claiming that not only Voynichese words are generated "without forcing anything" but also sequences of words? That's great (if true)!


RE: [split] Volvelles or Disks - AliciaNelPresente - 26-02-2026

(26-02-2026, 04:15 PM)nablator Wrote: You are not allowed to view links. Register or Login to view.
(26-02-2026, 03:54 PM)AliciaNelPresente Wrote: You are not allowed to view links. Register or Login to view.Macros, groups of words that are repeated.

Are you claiming that not only Voynichese words are generated "without forcing anything" but also sequences of words? That's great (if true)!

If that's what you understood from my posts or my last comment, let me tell you that you have a serious problem with reading comprehension  : )


RE: [split] Volvelles or Disks - nablator - 26-02-2026

(26-02-2026, 07:19 PM)AliciaNelPresente Wrote: You are not allowed to view links. Register or Login to view.If that's what you understood from my posts or my last comment, let me tell you that you have a serious problem with reading comprehension  : )

I wasn't expecting something totally unrelated to "Volvelles or Disks", the topic here. The recurring word sequences have been discussed in existing threads that you can find with the forum's search.


RE: [split] Volvelles or Disks - AliciaNelPresente - 26-02-2026

(26-02-2026, 08:52 PM)nablator Wrote: You are not allowed to view links. Register or Login to view.
(26-02-2026, 07:19 PM)AliciaNelPresente Wrote: You are not allowed to view links. Register or Login to view.If that's what you understood from my posts or my last comment, let me tell you that you have a serious problem with reading comprehension  : )

I wasn't expecting something totally unrelated to "Volvelles or Disks", the topic here. The recurring word sequences have been discussed in existing threads that you can find with the forum's search.

Oh Okii

I'll take another look at it. I skimmed through it before, but I'll give it some serious thought haha Thanks for the info Nablator! < 3