(26-05-2026, 11:28 PM)Dunsel wrote: You are not allowed to view links. Register or Login to view.PRECISELY... which is what my ledger does. Weighted letters.I was thinking more along the lines of an extension for “random.” Just for a quick suggestion, I decided to ask the AI for the first time. The basic idea seems viable, but of course the accuracy of the weighting would need to be verified.
Code:
# oshfdk_literal_generator_modified.py
# No external files required.
#
# Literal version of:
# 1) create 20-30 common words
# 2) create 10-20 common combinations
# 3) mix common words + common combinations + random longer words
# 4) random longer words occasionally repeat
#
# No mutation.
# No ledger.
# No syllables.
# No hidden morphology.
#
# FIX for Problem:
# Long words are no longer generated by flat random character sampling.
# Instead, positional character pools (INITIAL / MEDIAL / TERMINAL) with
# weights give each word a plausible frame:
# INITIAL – q, d, s, c dominate word-starts
# MEDIAL – o, e, a, i are the backbone of word bodies
# TERMINAL – n, y, r, l, m are the most common word-endings
import random
from collections import Counter
SEED = 42
PAGES = 100
WORDS_PER_PAGE = 95
WORDS_PER_LINE = 8
# ---------------------------------------------------------------------------
# Standard EVA-ish alphabet, sorted alphabetically.
# ---------------------------------------------------------------------------
VOYNICH_ALPHABET = "acdefhiklnoqrstxy"
LONG_WORD_MIN_LEN = 5
LONG_WORD_MAX_LEN = 12
LONG_WORD_REPEAT_CHANCE = 0.25
RECENT_LONG_WORD_WINDOW = 10
# ---------------------------------------------------------------------------
# Common words and combinations (unchanged)
# ---------------------------------------------------------------------------
COMMON_WORDS = [
"daiin", "dain", "ol", "or", "chol",
"chedy", "qokain", "qokeedy", "qotedy", "otedy",
"shey", "cthey", "cthol", "shol", "chor",
"dair", "qokair", "saiin", "aiin", "okain",
"sary", "okol", "qol", "qokal", "chdy",
]
COMMON_COMBINATIONS = [
["qokain", "daiin"],
["chol", "chedy"],
["qokeedy","qokedy"],
["ol", "chedy"],
["dain", "chol"],
["shey", "qokain"],
["cthey", "daiin"],
["shol", "chor"],
["qotedy", "qokain"],
["otedy", "ol"],
["saiin", "daiin"],
["qokair", "dair"],
["chol", "daiin"],
["cthol", "chedy"],
["or", "chol"],
]
# ---------------------------------------------------------------------------
# Weights are intentionally skewed to reflect the strong positional biases
# observed in Voynich script:
# INITIAL – q, d, s, c dominate word-starts
# MEDIAL – o, e, a, i are the backbone of word bodies
# TERMINAL – n, y, r, l, m are the most common word-endings
# ---------------------------------------------------------------------------
INITIAL_CHARS = {
"q": 8, "d": 7, "s": 6, "c": 6,
"o": 4, "a": 3, "f": 2, "r": 1,
"k": 1, "t": 1,
}
MEDIAL_CHARS = {
"o": 9, "e": 7, "a": 6, "i": 6,
"l": 4, "n": 3, "k": 3, "h": 3,
"r": 2, "t": 2, "d": 1, "s": 1,
}
TERMINAL_CHARS = {
"n": 8, "y": 8, "r": 6, "l": 5,
"m": 4, "s": 3, "d": 3,
}
def weighted_choice(weight_dict: dict) -> str:
"""Return a single character sampled according to integer weights."""
chars = list(weight_dict.keys())
weights = list(weight_dict.values())
return random.choices(chars, weights=weights, k=1)[0]
def make_random_long_word() -> str:
"""
Build a long word using positional character pools.
Structure:
[INITIAL char] + [MEDIAL body] + [TERMINAL char]
"""
length = random.randint(LONG_WORD_MIN_LEN, LONG_WORD_MAX_LEN)
# --- Word-initial character (positional pool) ---
word = weighted_choice(INITIAL_CHARS)
# --- Medial body (positional pool) ---
for _ in range(length - 2):
word += weighted_choice(MEDIAL_CHARS)
# --- Word-terminal character (positional pool) ---
word += weighted_choice(TERMINAL_CHARS)
return word
# ---------------------------------------------------------------------------
# Page generation (unchanged logic, fixed long-word generator plugged in)
# ---------------------------------------------------------------------------
def generate_page() -> list[str]:
page = []
recent_long_words = []
while len(page) < WORDS_PER_PAGE:
mode = random.choice(["common_word", "combination", "long_word"])
if mode == "common_word":
page.append(random.choice(COMMON_WORDS))
elif mode == "combination":
combo = random.choice(COMMON_COMBINATIONS)
page.extend(combo)
else: # long_word
if recent_long_words and random.random() < LONG_WORD_REPEAT_CHANCE:
word = random.choice(recent_long_words)
else:
word = make_random_long_word()
recent_long_words.append(word)
if len(recent_long_words) > RECENT_LONG_WORD_WINDOW:
recent_long_words.pop(0)
page.append(word)
return page[:WORDS_PER_PAGE]
# ---------------------------------------------------------------------------
# Analysis helpers (unchanged)
# ---------------------------------------------------------------------------
def char_ngrams(words: list[str], n: int) -> Counter:
counts = Counter()
for word in words:
if len(word) < n:
continue
for i in range(len(word) - n + 1):
counts[word[i:i + n]] += 1
return counts
def analyze(all_words: list[str]) -> None:
token_count = len(all_words)
type_count = len(set(all_words))
word_freq = Counter(all_words)
char_bigram_counts = char_ngrams(all_words, 2)
char_trigram_counts = char_ngrams(all_words, 3)
print()
print("=" * 60)
print("GLOBAL WORD STATISTICS")
print("=" * 60)
print()
print(f"Tokens : {token_count}")
print(f"Types : {type_count}")
print(f"TTR : {type_count / token_count:.4f}")
print(f"Hapax : {sum(1 for c in word_freq.values() if c == 1)}")
print()
print("Top 25 word tokens")
print()
for word, count in word_freq.most_common(25):
print(f"{word:15} {count}")
print()
print("=" * 60)
print("CHARACTER BIGRAMS")
print("=" * 60)
print()
for bg, count in char_bigram_counts.most_common(30):
print(f"{bg:5} {count}")
print()
print("=" * 60)
print("CHARACTER TRIGRAMS")
print("=" * 60)
print()
for tg, count in char_trigram_counts.most_common(30):
print(f"{tg:5} {count}")
def print_page(page_num: int, page: list[str]) -> None:
print()
print("=" * 60)
print(f"PAGE {page_num}")
print("=" * 60)
print()
for i in range(0, len(page), WORDS_PER_LINE):
print(" ".join(page[i:i + WORDS_PER_LINE]))
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main() -> None:
random.seed(SEED)
all_words = []
for page_num in range(1, PAGES + 1):
page = generate_page()
all_words.extend(page)
print_page(page_num, page)
analyze(all_words)
if __name__ == "__main__":
main()Sorry, I was a little slow in posting this. - Edit: Where exactly can I find “Ledger_Scribe1.json”?

