Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d043281a18 | |||
|
|
3db34fb634 | ||
| 813509f250 | |||
|
|
1e79c0c9e6 | ||
| 157892a95a |
6 changed files with 323 additions and 10 deletions
|
|
@ -20,7 +20,7 @@ register, and history.
|
|||
- Weksa owns the language-project pipeline: ontology projection, grammar,
|
||||
morphology, phonology, diachrony, rendering, glossing, and critique.
|
||||
- A future language project owns one inhabitant language. Airawa imperial
|
||||
register, pre-imperial Airawa registers, Sa'auei'a family-unit speech, ritual
|
||||
register, pre-imperial Airawa registers, Sa'ueia family-unit speech, ritual
|
||||
breeding-ground registers, and ecological-partner contact systems should not
|
||||
collapse into one universal naming soup.
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ surfaces:
|
|||
- Airawa continent: mother-tree memory networks, archival dependency,
|
||||
disconnected networks, and the Airawa Empire's biological-memetic
|
||||
infrastructure.
|
||||
- Sa'auei'a continent: advanced nomadic civilization, family-unit political
|
||||
- Sa'ueia continent: advanced nomadic civilization, family-unit political
|
||||
movement, sacred breeding-ground commons, remembered routes, and reciprocal
|
||||
ecological relationships.
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ rights, witnessed warrior promise, and honorable scar evidence. See
|
|||
### Provisional Airawa Phonology Notes
|
||||
|
||||
Airawa phonology is not finalized, but the current design pressure distinguishes
|
||||
it from Sa'auei'a softness. Airawa names should feel sharper, more dorsal and
|
||||
it from Sa'ueia softness. Airawa names should feel sharper, more dorsal and
|
||||
coronal, and more willing to close around hard edges.
|
||||
|
||||
Provisional tendencies:
|
||||
|
|
@ -58,9 +58,9 @@ Provisional tendencies:
|
|||
- sibilant and postalveolar fricatives should carry social texture: `s`, `z`,
|
||||
`sh` / `zh`;
|
||||
- `r` may be trilled in some Airawa registers;
|
||||
- syllables can tolerate clipped or closed shapes where Sa'auei'a forms would
|
||||
- syllables can tolerate clipped or closed shapes where Sa'ueia forms would
|
||||
flow longer and wetter;
|
||||
- avoid making native Airawa names read like smooth Sa'auei'a route names unless
|
||||
- avoid making native Airawa names read like smooth Sa'ueia route names unless
|
||||
the form is explicitly a loan, exonym, or cross-continental register.
|
||||
|
||||
The current room consensus pushes this further for introductory speech: Airawa
|
||||
|
|
@ -75,9 +75,9 @@ voiced postalveolar fricative /zh/ [ʒ]. The name is being considered for a
|
|||
Zyphos Face drawn from a non-imperial Airawa archival/fungal-road tradition: an
|
||||
archivist shaped by memory as kinship, not content.
|
||||
|
||||
## Sa'auei'a Pressures
|
||||
## Sa'ueia Pressures
|
||||
|
||||
Sa'auei'a language work must account for:
|
||||
Sa'ueia language work must account for:
|
||||
|
||||
- advanced nomadic civilization without permanent urban dominance
|
||||
- squad-sized family units
|
||||
|
|
@ -88,10 +88,46 @@ Sa'auei'a language work must account for:
|
|||
- reciprocity routes and seasonal corridors
|
||||
- camps, commons, and partner sites as distinct name targets
|
||||
|
||||
The Sa'auei'a continent should not read as empty wilderness. Its language should
|
||||
The Sa'ueia continent should not read as empty wilderness. Its language should
|
||||
have ways to name civilization that moves, recurs, bargains, and is remembered by
|
||||
ecology.
|
||||
|
||||
## Sa'ueia Sound (Proposal For Deru To Cut)
|
||||
|
||||
Status: proposal. Deru approved the outline in #zyphos on 2026-11-04; only the
|
||||
species name Sa'ueia is canon. Everything below is a working draft in
|
||||
`sa-ueia/table.yaml`, each entry marked `deru` or `weksa-placeholder`, and
|
||||
`sa-ueia/check.py` enforces what is mechanical.
|
||||
|
||||
Taken from Deru's words:
|
||||
|
||||
- five vowels: `a e i o u`;
|
||||
- no `h`, `q`, `y`; `k` where a `q` might have been;
|
||||
- short words, soft and open;
|
||||
- suffixing morphology; the one suffix so far is `-um`, a verb suffix;
|
||||
- identical letters merge at a morpheme join: `weru` + `um` gives `werum`,
|
||||
`ila` + `werum` gives `ilawerum` (walk of knowledge);
|
||||
- a vowel only where one is needed.
|
||||
|
||||
First roots, all Deru's: `ila` walk, `weru` know, `Olune` breeding ground
|
||||
(unanalyzed).
|
||||
|
||||
Proposed, for Deru to accept or cut:
|
||||
|
||||
- the apostrophe in Sa'ueia is orthographic, and is not counted as a letter;
|
||||
- "short" means at most 10 letters in a joined word, as a warning only;
|
||||
- no doubled letter inside a morpheme, so a double can only arise at a join
|
||||
and the merge rule removes it.
|
||||
|
||||
Open, not guessed:
|
||||
|
||||
- the consonant inventory. Only `l m n r s w` appear in Deru's words so far,
|
||||
and that is attestation, not an inventory;
|
||||
- whether the know root is `weru` or `wer-` (both give `werum`);
|
||||
- syllable shapes, stress, and what "vowel only where needed" means once the
|
||||
inventory is known (the checker does not test it);
|
||||
- the phonological role of the apostrophe.
|
||||
|
||||
## Naming Brief Shape
|
||||
|
||||
A Weksa naming brief should capture at least:
|
||||
|
|
|
|||
102
settings/eusocial-interbeing/sa-ueia/check.py
Normal file
102
settings/eusocial-interbeing/sa-ueia/check.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Sa'ueia workshop checker: join morphemes by table.yaml's rules, flag violations.
|
||||
|
||||
Reads table.yaml next to this file (or a path argument). The consonant inventory
|
||||
is open, so no consonant is ever flagged; only rules in the table are enforced.
|
||||
Exit status is 1 when any error is found; warnings do not fail the run.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
TABLE = Path(__file__).with_name("table.yaml")
|
||||
PROVENANCES = {"deru", "weksa-placeholder"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
severity: str # "error" | "warning"
|
||||
where: str
|
||||
message: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.severity}: {self.where}: {self.message}"
|
||||
|
||||
|
||||
def letters(form: str) -> str:
|
||||
return form.replace("'", "").lower()
|
||||
|
||||
|
||||
def join(morphemes: list[str]) -> str:
|
||||
"""Concatenate, merging one identical letter at each join."""
|
||||
out = ""
|
||||
for m in morphemes:
|
||||
if out and m and out[-1] == m[0]:
|
||||
m = m[1:]
|
||||
out += m
|
||||
return out
|
||||
|
||||
|
||||
def check_form(form: str, rules: dict, where: str, *, inside_morpheme: bool = False) -> list[Finding]:
|
||||
found = []
|
||||
text = letters(form)
|
||||
for ch in rules["forbidden_letters"]["value"]:
|
||||
if ch in text:
|
||||
found.append(Finding("error", where, f"{form!r} contains forbidden letter {ch!r}"))
|
||||
for a, b in zip(text, text[1:]):
|
||||
if a == b and (not inside_morpheme or rules["no_doubled_letters_inside_morpheme"]["value"]):
|
||||
found.append(Finding("error", where, f"{form!r} has doubled {a!r}"))
|
||||
limit = rules["max_word_letters"]
|
||||
if not inside_morpheme and len(text) > limit["value"]:
|
||||
found.append(Finding(limit["severity"], where, f"{form!r} is {len(text)} letters, over {limit['value']}"))
|
||||
return found
|
||||
|
||||
|
||||
def check_table(table: dict) -> list[Finding]:
|
||||
rules = table["rules"]
|
||||
found = []
|
||||
for r_name, r in rules.items():
|
||||
if r.get("provenance") not in PROVENANCES:
|
||||
found.append(Finding("error", f"rules.{r_name}", "missing or unknown provenance"))
|
||||
if table["consonants"]["inventory"] != "open":
|
||||
found.append(Finding("error", "consonants", "inventory is not Deru's to guess; keep it open until given"))
|
||||
forms: dict[str, str] = {}
|
||||
for kind in ("roots", "suffixes"):
|
||||
for entry in table[kind]:
|
||||
form = entry["form"]
|
||||
where = f"{kind}.{form}"
|
||||
if entry.get("provenance") not in PROVENANCES:
|
||||
found.append(Finding("error", where, "missing or unknown provenance"))
|
||||
forms[form] = kind
|
||||
found += check_form(form, rules, where, inside_morpheme=True)
|
||||
for comp in table["compositions"]:
|
||||
where = "compositions." + "+".join(comp["morphemes"])
|
||||
if comp.get("provenance") not in PROVENANCES:
|
||||
found.append(Finding("error", where, "missing or unknown provenance"))
|
||||
for m in comp["morphemes"]:
|
||||
if m not in forms and not any(m == f2 for f2 in (c["expected"] for c in table["compositions"])):
|
||||
found.append(Finding("error", where, f"morpheme {m!r} is in no table row or earlier composition"))
|
||||
got = join(comp["morphemes"])
|
||||
if got != comp["expected"]:
|
||||
found.append(Finding("error", where, f"joins to {got!r}, table says {comp['expected']!r}"))
|
||||
found += check_form(comp["expected"], rules, where)
|
||||
return found
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
path = Path(argv[1]) if len(argv) > 1 else TABLE
|
||||
findings = check_table(yaml.safe_load(path.read_text(encoding="utf-8")))
|
||||
for f in findings:
|
||||
print(f)
|
||||
print(f"{sum(f.severity == 'error' for f in findings)} error(s), "
|
||||
f"{sum(f.severity == 'warning' for f in findings)} warning(s)")
|
||||
return 1 if any(f.severity == "error" for f in findings) else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
81
settings/eusocial-interbeing/sa-ueia/table.yaml
Normal file
81
settings/eusocial-interbeing/sa-ueia/table.yaml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Sa'ueia workshop table, first cut.
|
||||
# Source: Deru's words in #zyphos, 2026-11-04. Setting owner: Deru (the Zyphos vault).
|
||||
# Every entry carries provenance:
|
||||
# deru - said by Deru, or part of the sound outline Deru approved
|
||||
# weksa-placeholder - Weksa's stand-in so the checker can run; Deru may cut it
|
||||
# Only the species name Sa'ueia is canon. Everything else here is a working
|
||||
# draft, even when its provenance is deru.
|
||||
|
||||
roots:
|
||||
- form: ila
|
||||
gloss: walk
|
||||
provenance: deru
|
||||
- form: weru
|
||||
gloss: know
|
||||
provenance: deru
|
||||
note: >
|
||||
Deru gave "werum" (knowledge, maybe a verb) and was asked whether -um
|
||||
suffixes wer- or weru. "weru" is the reading under which the join rule
|
||||
does visible work (weru + um -> werum); wer- gives the same surface form.
|
||||
Open for Deru.
|
||||
- form: olune
|
||||
gloss: breeding ground
|
||||
provenance: deru
|
||||
analysis: unanalyzed # no morpheme boundaries known
|
||||
- form: sa'ueia
|
||||
gloss: the species name
|
||||
provenance: deru
|
||||
canon: true
|
||||
analysis: unanalyzed
|
||||
note: Apostrophe is part of the canon spelling; its phonological role is open.
|
||||
|
||||
suffixes:
|
||||
- form: um
|
||||
gloss: verb suffix
|
||||
provenance: deru
|
||||
|
||||
rules:
|
||||
vowels:
|
||||
value: [a, e, i, o, u]
|
||||
provenance: deru
|
||||
forbidden_letters:
|
||||
value: [h, q, y]
|
||||
provenance: deru
|
||||
note: k is used where q might have been.
|
||||
join_merge_identical:
|
||||
value: true
|
||||
provenance: deru
|
||||
note: >
|
||||
When one morpheme ends in the letter the next begins with, they merge to
|
||||
one letter at the join (weru + um -> werum, ila + werum -> ilawerum).
|
||||
morphology_direction:
|
||||
value: suffixing
|
||||
provenance: deru
|
||||
short_words:
|
||||
provenance: deru
|
||||
checked: false # no number from Deru
|
||||
max_word_letters:
|
||||
value: 10
|
||||
provenance: weksa-placeholder
|
||||
severity: warning
|
||||
note: Stand-in for "short words"; apostrophes not counted. Deru sets the real bound.
|
||||
no_doubled_letters_inside_morpheme:
|
||||
value: true
|
||||
provenance: weksa-placeholder
|
||||
note: Follows from the merge rule only if doubles never occur inside a morpheme either.
|
||||
vowel_only_where_needed:
|
||||
provenance: deru
|
||||
checked: false # not mechanically decidable without the consonant inventory
|
||||
|
||||
consonants:
|
||||
inventory: open # Deru has not given it. The checker does not guess.
|
||||
seen_in_deru_words: [l, m, n, r, s, w] # attested above, not an inventory
|
||||
|
||||
# Joined forms the checker must reproduce. Each: morphemes in order, expected surface.
|
||||
compositions:
|
||||
- morphemes: [weru, um]
|
||||
expected: werum
|
||||
provenance: deru # "werum" is Deru's form
|
||||
- morphemes: [ila, werum]
|
||||
expected: ilawerum
|
||||
provenance: deru # "Ilawerum", walk of knowledge
|
||||
94
settings/eusocial-interbeing/sa-ueia/test_check.py
Normal file
94
settings/eusocial-interbeing/sa-ueia/test_check.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import copy
|
||||
import unittest
|
||||
|
||||
import yaml
|
||||
|
||||
import check
|
||||
|
||||
|
||||
def table():
|
||||
return yaml.safe_load(check.TABLE.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def errors(t):
|
||||
return [f for f in check.check_table(t) if f.severity == "error"]
|
||||
|
||||
|
||||
class JoinTests(unittest.TestCase):
|
||||
def test_identical_letters_merge_at_join(self):
|
||||
self.assertEqual(check.join(["weru", "um"]), "werum")
|
||||
self.assertEqual(check.join(["ila", "werum"]), "ilawerum")
|
||||
|
||||
def test_merge_is_one_letter_not_all(self):
|
||||
self.assertEqual(check.join(["ila", "aba"]), "ilaba")
|
||||
self.assertEqual(check.join(["ila", "ala"]), "ilala")
|
||||
|
||||
def test_different_letters_concatenate(self):
|
||||
self.assertEqual(check.join(["ila", "um"]), "ilaum")
|
||||
|
||||
def test_merge_applies_only_at_joins(self):
|
||||
# a doubled letter inside a morpheme is not collapsed by join
|
||||
self.assertEqual(check.join(["aa", "b"]), "aab")
|
||||
|
||||
|
||||
class TableTests(unittest.TestCase):
|
||||
def test_shipped_table_is_clean(self):
|
||||
self.assertEqual(errors(table()), [])
|
||||
|
||||
def test_forbidden_letter_in_root_flagged(self):
|
||||
t = table()
|
||||
t["roots"].append({"form": "haru", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
self.assertTrue(any("forbidden letter 'h'" in str(f) for f in errors(t)))
|
||||
|
||||
def test_q_and_y_flagged(self):
|
||||
for ch in "qy":
|
||||
t = table()
|
||||
t["roots"].append({"form": f"a{ch}a", "gloss": "x", "provenance": "deru"})
|
||||
self.assertTrue(any(f"forbidden letter '{ch}'" in str(f) for f in errors(t)), ch)
|
||||
|
||||
def test_k_allowed(self):
|
||||
t = table()
|
||||
t["roots"].append({"form": "kela", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
self.assertEqual(errors(t), [])
|
||||
|
||||
def test_missing_or_unknown_provenance_flagged(self):
|
||||
for bad in (None, "guess"):
|
||||
t = table()
|
||||
t["suffixes"][0]["provenance"] = bad
|
||||
self.assertTrue(any("provenance" in str(f) for f in errors(t)), bad)
|
||||
|
||||
def test_unmerged_composition_flagged(self):
|
||||
t = table()
|
||||
t["compositions"][0]["expected"] = "weruum"
|
||||
msgs = [str(f) for f in errors(t)]
|
||||
self.assertTrue(any("joins to 'werum'" in m for m in msgs))
|
||||
self.assertTrue(any("doubled 'u'" in m for m in msgs))
|
||||
|
||||
def test_doubled_letter_inside_morpheme_flagged(self):
|
||||
t = table()
|
||||
t["roots"].append({"form": "ilaa", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
self.assertTrue(any("doubled 'a'" in str(f) for f in errors(t)))
|
||||
|
||||
def test_unknown_morpheme_in_composition_flagged(self):
|
||||
t = table()
|
||||
t["compositions"].append({"morphemes": ["ila", "zo"], "expected": "ilazo", "provenance": "weksa-placeholder"})
|
||||
self.assertTrue(any("'zo'" in str(f) for f in errors(t)))
|
||||
|
||||
def test_long_word_is_warning_not_error(self):
|
||||
t = table()
|
||||
t["compositions"].append({"morphemes": ["ila", "werum", "ila", "werum"], "expected": "ilawerumilawerum",
|
||||
"provenance": "weksa-placeholder"})
|
||||
self.assertEqual(errors(t), [])
|
||||
self.assertTrue(any(f.severity == "warning" for f in check.check_table(t)))
|
||||
|
||||
def test_consonant_inventory_must_stay_open(self):
|
||||
t = table()
|
||||
t["consonants"]["inventory"] = ["l", "m"]
|
||||
self.assertTrue(any("inventory" in str(f) for f in errors(t)))
|
||||
|
||||
def test_apostrophe_is_not_a_letter(self):
|
||||
self.assertEqual(check.letters("Sa'ueia"), "saueia")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -221,10 +221,10 @@ open_questions:
|
|||
- What is the minimal Rust to Dust language-project slice that proves the
|
||||
engine without hard-coding the setting into it?
|
||||
- What minimal Eusocial Interbeing language-project slice can prove naming for
|
||||
Airawa, Sa'auei'a, ecological partner, and imperial registers without
|
||||
Airawa, Sa'ueia, ecological partner, and imperial registers without
|
||||
collapsing them into one universal language?
|
||||
- Which Zyphos language project should be first: Airawa, imperial Airawa,
|
||||
Sa'auei'a, or a narrow naming-brief interlingua?
|
||||
Sa'ueia, or a narrow naming-brief interlingua?
|
||||
- What is the minimal Airawa starter lexicon and permission model needed to
|
||||
translate the first chieftain/visitor/mother-tree scene honestly?
|
||||
- How should Airawa encode honorable scars: bodily evidence, honor claim, debt
|
||||
|
|
|
|||
Loading…
Reference in a new issue