Deru phonotactics in the Sa'ueia/Airawa checker; names file; briefs updated #9
7 changed files with 353 additions and 31 deletions
|
|
@ -52,14 +52,22 @@ Airawa phonology is not finalized, but the current design pressure distinguishes
|
|||
it from Sa'ueia softness. Airawa names should feel sharper, more dorsal and
|
||||
coronal, and more willing to close around hard edges.
|
||||
|
||||
Deru ruled on 2026-11-06 that the following bind **both** Airawa and Sa'ueia:
|
||||
two consonants never meet, two identical vowels never meet, and `k` is used
|
||||
where `q` might have been (so `q` is not an Airawa letter). The tendencies below
|
||||
are read under that rule: "closed syllables" survives only as a word-final
|
||||
consonant, `q` is retired from the plosive list, and `sh` / `zh` count as two
|
||||
consonants until Deru says they are single sounds (`th` is not a cluster, as
|
||||
relayed). `sa-ueia/check.py` enforces it.
|
||||
|
||||
Provisional tendencies:
|
||||
|
||||
- dorsal and coronal plosives should be prominent: `k`, `g`, `q`, `t`, `d`;
|
||||
- dorsal and coronal plosives should be prominent: `k`, `g`, `t`, `d` (`q` retired);
|
||||
- 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'ueia forms would
|
||||
flow longer and wetter;
|
||||
- syllables can tolerate clipped shapes (a word-final consonant) where Sa'ueia
|
||||
forms would flow longer and wetter;
|
||||
- avoid making native Airawa names read like smooth Sa'ueia route names unless
|
||||
the form is explicitly a loan, exonym, or cross-continental register.
|
||||
|
||||
|
|
@ -102,7 +110,10 @@ species name Sa'ueia is canon. Everything below is a working draft in
|
|||
Taken from Deru's words:
|
||||
|
||||
- five vowels: `a e i o u`;
|
||||
- no `h`, `q`, `y`; `k` where a `q` might have been;
|
||||
- no `h`, `q`, `y`; `k` where a `q` might have been (2026-11-06: `q` is out of
|
||||
Airawa too);
|
||||
- two consonants never meet; two identical vowels never meet (2026-11-06, both
|
||||
languages);
|
||||
- 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`,
|
||||
|
|
@ -116,8 +127,8 @@ 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.
|
||||
- (withdrawn 2026-11-06: replaced by Deru's rules that no two consonants and
|
||||
no two identical vowels meet, in Sa'ueia and Airawa.)
|
||||
|
||||
Open, not guessed:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Sa'ueia workshop checker: join morphemes by table.yaml's rules, flag violations.
|
||||
"""Sa'ueia and Airawa 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.
|
||||
usage: check.py [table.yaml] [--names names.yaml]
|
||||
|
||||
Reads table.yaml next to this file (or a path argument). With --names it also
|
||||
joins every proposed name in that file from its morphemes and runs the result
|
||||
through the same rules for its language. The consonant inventory is open, so no
|
||||
consonant is ever flagged for being unlisted; only rules in the table are
|
||||
enforced. Deru's phonotactics (2026-11-06) bind both languages: no two
|
||||
consonants adjacent, no two identical vowels adjacent, k and never q.
|
||||
Exit status is 1 when any error is found; warnings do not fail the run.
|
||||
"""
|
||||
|
||||
|
|
@ -16,6 +23,7 @@ import yaml
|
|||
|
||||
TABLE = Path(__file__).with_name("table.yaml")
|
||||
PROVENANCES = {"deru", "weksa-placeholder"}
|
||||
LANGUAGES = ("saueia", "airawa")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -42,17 +50,40 @@ def join(morphemes: list[str]) -> str:
|
|||
return out
|
||||
|
||||
|
||||
def check_form(form: str, rules: dict, where: str, *, inside_morpheme: bool = False) -> list[Finding]:
|
||||
def units(text: str, rules: dict) -> list[tuple[str, bool]]:
|
||||
"""Split letters into (unit, is_vowel); a listed digraph is one consonant."""
|
||||
vowels = set(rules["vowels"]["value"])
|
||||
digraphs = rules["no_adjacent_consonants"].get("digraphs", [])
|
||||
out, i = [], 0
|
||||
while i < len(text):
|
||||
if text[i : i + 2] in digraphs:
|
||||
out.append((text[i : i + 2], False))
|
||||
i += 2
|
||||
else:
|
||||
out.append((text[i], text[i] in vowels))
|
||||
i += 1
|
||||
return out
|
||||
|
||||
|
||||
def forbidden(rules: dict, language: str) -> list[str]:
|
||||
r = rules["forbidden_letters"]
|
||||
return list(r["value"]) if language == "saueia" else list(r.get("airawa", []))
|
||||
|
||||
|
||||
def check_form(form: str, rules: dict, where: str, *, inside_morpheme: bool = False,
|
||||
language: str = "saueia") -> list[Finding]:
|
||||
found = []
|
||||
text = letters(form)
|
||||
for ch in rules["forbidden_letters"]["value"]:
|
||||
for ch in forbidden(rules, language):
|
||||
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}"))
|
||||
for (a, a_vowel), (b, b_vowel) in zip(units(text, rules), units(text, rules)[1:]):
|
||||
if not a_vowel and not b_vowel and rules["no_adjacent_consonants"]["value"]:
|
||||
found.append(Finding("error", where, f"{form!r} has adjacent consonants {a + b!r}"))
|
||||
if a_vowel and b_vowel and a == b and rules["no_identical_adjacent_vowels"]["value"]:
|
||||
found.append(Finding("error", where, f"{form!r} has identical adjacent vowels {a + b!r}"))
|
||||
limit = rules["max_word_letters"]
|
||||
if not inside_morpheme and len(text) > limit["value"]:
|
||||
if language == "saueia" and 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
|
||||
|
||||
|
|
@ -88,9 +119,56 @@ def check_table(table: dict) -> list[Finding]:
|
|||
return found
|
||||
|
||||
|
||||
def check_names(table: dict, doc: dict) -> list[Finding]:
|
||||
"""Join each proposed name from its morphemes and run it through the rules.
|
||||
|
||||
Morphemes come from the table (Sa'ueia only) and from the roots in `doc`,
|
||||
per language. A name's written form must equal the join, ignoring case.
|
||||
"""
|
||||
rules = table["rules"]
|
||||
found = []
|
||||
known: dict[str, dict[str, str]] = {lang: {} for lang in LANGUAGES}
|
||||
for kind in ("roots", "suffixes"):
|
||||
for entry in table[kind]:
|
||||
known["saueia"][entry["form"]] = "table"
|
||||
for lang, entries in doc.get("roots", {}).items():
|
||||
if lang not in LANGUAGES:
|
||||
found.append(Finding("error", f"roots.{lang}", "unknown language"))
|
||||
continue
|
||||
for entry in entries:
|
||||
where = f"roots.{lang}.{entry['form']}"
|
||||
if entry.get("provenance") not in PROVENANCES:
|
||||
found.append(Finding("error", where, "missing or unknown provenance"))
|
||||
known[lang][entry["form"]] = "names"
|
||||
found += check_form(entry["form"], rules, where, inside_morpheme=True, language=lang)
|
||||
for name in doc["names"]:
|
||||
lang = name.get("language")
|
||||
where = f"names.{name['form']}"
|
||||
if lang not in LANGUAGES:
|
||||
found.append(Finding("error", where, "missing or unknown language"))
|
||||
continue
|
||||
for m in name["morphemes"]:
|
||||
if m not in known[lang]:
|
||||
found.append(Finding("error", where, f"morpheme {m!r} is in no {lang} row"))
|
||||
got = join(name["morphemes"])
|
||||
if got != letters(name["form"]):
|
||||
found.append(Finding("error", where, f"joins to {got!r}, name says {name['form']!r}"))
|
||||
found += check_form(name["form"], rules, where, language=lang)
|
||||
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")))
|
||||
args = argv[1:]
|
||||
names_path = None
|
||||
if "--names" in args:
|
||||
i = args.index("--names")
|
||||
names_path = Path(args[i + 1])
|
||||
del args[i : i + 2]
|
||||
path = Path(args[0]) if args else TABLE
|
||||
table = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||
findings = check_table(table)
|
||||
if names_path:
|
||||
findings += check_names(table, yaml.safe_load(names_path.read_text(encoding="utf-8")))
|
||||
for f in findings:
|
||||
print(f)
|
||||
print(f"{sum(f.severity == 'error' for f in findings)} error(s), "
|
||||
|
|
|
|||
45
settings/eusocial-interbeing/sa-ueia/names.yaml
Normal file
45
settings/eusocial-interbeing/sa-ueia/names.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Proposed names, run through check.py by `check.py --names names.yaml`.
|
||||
# Mirrors notes/inhabitant-names-draft.md in the Zyphos repo (PR 2). Every name
|
||||
# is a draft; none is canon. Deru's roots are in table.yaml and are not repeated.
|
||||
# Each name lists its morphemes; the checker joins them (identical letters merge
|
||||
# at a join) and applies Deru's rules (2026-11-06), which bind both languages:
|
||||
# no adjacent consonants, no identical adjacent vowels, k and never q.
|
||||
|
||||
roots:
|
||||
saueia:
|
||||
- {form: muna, gloss: memory; to remember, provenance: weksa-placeholder}
|
||||
- {form: sela, gloss: gift; giving, provenance: weksa-placeholder}
|
||||
- {form: rena, gloss: return; come back, provenance: weksa-placeholder}
|
||||
- {form: kasu, gloss: ecological partner, provenance: weksa-placeholder}
|
||||
- {form: mano, gloss: kin, provenance: weksa-placeholder}
|
||||
- {form: sulo, gloss: stay; be rooted, provenance: weksa-placeholder}
|
||||
# Airawa roots were closed syllables (gor, dak, zeg, sar, tez, -ur). Those put
|
||||
# two consonants together at every join, so each is respelled open here. The
|
||||
# tribe-linked-to-a-matriarch-tree fact (Deru) is not a root: see the briefs.
|
||||
airawa:
|
||||
- {form: gora, gloss: mother-tree body, provenance: weksa-placeholder}
|
||||
- {form: daka, gloss: keep; hold in memory, provenance: weksa-placeholder}
|
||||
- {form: zege, gloss: sever, provenance: weksa-placeholder}
|
||||
- {form: sara, gloss: web; network, provenance: weksa-placeholder}
|
||||
- {form: teza, gloss: order; ordinance, provenance: weksa-placeholder}
|
||||
- {form: ur, gloss: pending; awaiting (imperial register suffix), provenance: weksa-placeholder}
|
||||
- {form: kerath, gloss: obligation; sworn bond (the slice's qerath without q), provenance: weksa-placeholder}
|
||||
|
||||
names:
|
||||
# Sa'ueia
|
||||
- {form: Manoila, language: saueia, morphemes: [mano, ila]}
|
||||
- {form: Selamuna, language: saueia, morphemes: [sela, muna]}
|
||||
- {form: Ilamuna, language: saueia, morphemes: [ila, muna]}
|
||||
- {form: Ilarena, language: saueia, morphemes: [ila, rena]}
|
||||
- {form: Munaila, language: saueia, morphemes: [muna, ila]}
|
||||
- {form: Kasu, language: saueia, morphemes: [kasu]}
|
||||
- {form: Kasumuna, language: saueia, morphemes: [kasu, muna]}
|
||||
- {form: Olunerena, language: saueia, morphemes: [olune, rena]}
|
||||
- {form: Munasulo, language: saueia, morphemes: [muna, sulo]}
|
||||
# Airawa
|
||||
- {form: Goradaka, language: airawa, morphemes: [gora, daka]}
|
||||
- {form: Gorakerath, language: airawa, morphemes: [gora, kerath]}
|
||||
- {form: Zegedaka, language: airawa, morphemes: [zege, daka]}
|
||||
- {form: Gorasara, language: airawa, morphemes: [gora, sara]}
|
||||
- {form: Tezasara, language: airawa, morphemes: [teza, sara]}
|
||||
- {form: Saratezaur, language: airawa, morphemes: [sara, teza, ur]}
|
||||
|
|
@ -39,9 +39,27 @@ rules:
|
|||
value: [a, e, i, o, u]
|
||||
provenance: deru
|
||||
forbidden_letters:
|
||||
value: [h, q, y]
|
||||
value: [h, q, y] # Sa'ueia
|
||||
airawa: [q] # Deru, 2026-11-06: q is not used in Airawa either; no word on h or y
|
||||
provenance: deru
|
||||
note: k is used where q might have been.
|
||||
note: k is used where q might have been, in both languages.
|
||||
no_adjacent_consonants:
|
||||
value: true
|
||||
applies_to: [saueia, airawa]
|
||||
provenance: deru
|
||||
digraphs: [th]
|
||||
note: >
|
||||
Deru, 2026-11-06: two consonants never meet, in both languages. Deru also
|
||||
said "th" is not a cluster; read here as: "th" is one consonant, so it
|
||||
does not count as a meeting. Whether "sh" and "zh" are digraphs too is
|
||||
open; until Deru says, they are two consonants and are flagged. An
|
||||
apostrophe does not separate letters. A join that leaves two different
|
||||
consonants adjacent is an error in the joined word.
|
||||
no_identical_adjacent_vowels:
|
||||
value: true
|
||||
applies_to: [saueia, airawa]
|
||||
provenance: deru
|
||||
note: "Deru, 2026-11-06: two identical vowels never meet, in both languages."
|
||||
join_merge_identical:
|
||||
value: true
|
||||
provenance: deru
|
||||
|
|
@ -59,10 +77,6 @@ rules:
|
|||
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
|
||||
|
|
|
|||
|
|
@ -62,12 +62,54 @@ class TableTests(unittest.TestCase):
|
|||
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))
|
||||
self.assertTrue(any("identical adjacent vowels 'uu'" in m for m in msgs))
|
||||
|
||||
def test_doubled_letter_inside_morpheme_flagged(self):
|
||||
def test_identical_vowels_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)))
|
||||
self.assertTrue(any("identical adjacent vowels 'aa'" in str(f) for f in errors(t)))
|
||||
|
||||
def test_different_adjacent_vowels_allowed(self):
|
||||
t = table()
|
||||
t["roots"].append({"form": "iela", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
self.assertEqual(errors(t), [])
|
||||
|
||||
def test_adjacent_consonants_inside_morpheme_flagged(self):
|
||||
t = table()
|
||||
t["roots"].append({"form": "ilma", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
self.assertTrue(any("adjacent consonants 'lm'" in str(f) for f in errors(t)))
|
||||
|
||||
def test_doubled_consonant_is_adjacent_consonants(self):
|
||||
t = table()
|
||||
t["roots"].append({"form": "illa", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
self.assertTrue(any("adjacent consonants 'll'" in str(f) for f in errors(t)))
|
||||
|
||||
def test_join_that_leaves_two_consonants_flagged(self):
|
||||
t = table()
|
||||
t["roots"].append({"form": "sal", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
t["roots"].append({"form": "ma", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
t["compositions"].append({"morphemes": ["sal", "ma"], "expected": "salma", "provenance": "weksa-placeholder"})
|
||||
self.assertTrue(any("adjacent consonants 'lm'" in str(f) for f in errors(t)))
|
||||
|
||||
def test_consonant_merge_at_join_is_clean(self):
|
||||
t = table()
|
||||
t["roots"].append({"form": "sal", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
t["roots"].append({"form": "la", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
t["compositions"].append({"morphemes": ["sal", "la"], "expected": "sala", "provenance": "weksa-placeholder"})
|
||||
self.assertEqual(errors(t), [])
|
||||
|
||||
def test_apostrophe_does_not_separate_consonants(self):
|
||||
rules = table()["rules"]
|
||||
self.assertTrue(check.check_form("al'ma", rules, "x"))
|
||||
|
||||
def test_th_is_one_consonant_but_sh_is_two(self):
|
||||
rules = table()["rules"]
|
||||
self.assertEqual(check.check_form("kerath", rules, "x", language="airawa"), [])
|
||||
self.assertTrue(check.check_form("kesha", rules, "x", language="airawa"))
|
||||
self.assertTrue(check.check_form("kethr", rules, "x", language="airawa"))
|
||||
|
||||
def test_final_consonant_is_not_a_cluster(self):
|
||||
self.assertEqual(check.check_form("werum", table()["rules"], "x"), [])
|
||||
|
||||
def test_unknown_morpheme_in_composition_flagged(self):
|
||||
t = table()
|
||||
|
|
@ -86,9 +128,68 @@ class TableTests(unittest.TestCase):
|
|||
t["consonants"]["inventory"] = ["l", "m"]
|
||||
self.assertTrue(any("inventory" in str(f) for f in errors(t)))
|
||||
|
||||
def test_the_rules_bind_both_languages(self):
|
||||
rules = table()["rules"]
|
||||
for lang in check.LANGUAGES:
|
||||
self.assertTrue(check.check_form("qara", rules, "x", language=lang), lang)
|
||||
self.assertTrue(check.check_form("goraa", rules, "x", language=lang), lang)
|
||||
self.assertTrue(check.check_form("gordak", rules, "x", language=lang), lang)
|
||||
|
||||
def test_h_and_y_are_not_forbidden_for_airawa(self):
|
||||
rules = table()["rules"]
|
||||
self.assertEqual(check.check_form("hayo", rules, "x", language="airawa"), [])
|
||||
self.assertTrue(check.check_form("hayo", rules, "x", language="saueia"))
|
||||
|
||||
def test_apostrophe_is_not_a_letter(self):
|
||||
self.assertEqual(check.letters("Sa'ueia"), "saueia")
|
||||
|
||||
|
||||
NAMES = check.Path(__file__).with_name("names.yaml")
|
||||
|
||||
|
||||
def names():
|
||||
return yaml.safe_load(NAMES.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def name_errors(doc):
|
||||
return [f for f in check.check_names(table(), doc) if f.severity == "error"]
|
||||
|
||||
|
||||
class NameTests(unittest.TestCase):
|
||||
def test_every_proposed_name_passes(self):
|
||||
self.assertEqual(name_errors(names()), [])
|
||||
|
||||
def test_the_closed_syllable_airawa_drafts_all_fail(self):
|
||||
# The first draft's Airawa names, built from closed-syllable roots.
|
||||
old = {"Gordak": ["gor", "dak"], "Gorqerath": ["gor", "qerath"], "Zegdak": ["zeg", "dak"],
|
||||
"Gorsar": ["gor", "sar"], "Tezsar": ["tez", "sar"], "Sartezur": ["sar", "tez", "ur"]}
|
||||
rules = table()["rules"]
|
||||
for form, parts in old.items():
|
||||
self.assertTrue(check.check_form(form, rules, form, language="airawa"), form)
|
||||
|
||||
def test_unknown_morpheme_flagged(self):
|
||||
doc = names()
|
||||
doc["names"].append({"form": "Zoila", "language": "saueia", "morphemes": ["zo", "ila"]})
|
||||
self.assertTrue(any("'zo'" in str(f) for f in name_errors(doc)))
|
||||
|
||||
def test_name_must_equal_its_join(self):
|
||||
doc = names()
|
||||
doc["names"].append({"form": "Ilawerum", "language": "saueia", "morphemes": ["ila", "weru"]})
|
||||
self.assertTrue(any("joins to 'ilaweru'" in str(f) for f in name_errors(doc)))
|
||||
|
||||
def test_a_cluster_in_a_proposed_name_is_flagged(self):
|
||||
doc = names()
|
||||
doc["roots"]["airawa"].append({"form": "dak", "gloss": "x", "provenance": "weksa-placeholder"})
|
||||
doc["names"].append({"form": "Goradak", "language": "airawa", "morphemes": ["gora", "dak"]})
|
||||
self.assertEqual(name_errors(doc), []) # open join, final consonant ok
|
||||
doc["names"].append({"form": "Gordak", "language": "airawa", "morphemes": ["gor", "dak"]})
|
||||
self.assertTrue(any("adjacent consonants 'rd'" in str(f) for f in name_errors(doc)))
|
||||
|
||||
def test_unknown_language_flagged(self):
|
||||
doc = names()
|
||||
doc["names"].append({"form": "Kasu", "language": "klingon", "morphemes": ["kasu"]})
|
||||
self.assertTrue(any("language" in str(f) for f in name_errors(doc)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,79 @@ from the Zyphos vault (`Species/Airawa`, `Species/Matriarch Trees`,
|
|||
Both brief sets wait on Meta's answers to `vq-first-language` and
|
||||
`vq-name-scope`. Where a brief's scope depends on them it says so.
|
||||
|
||||
## Deru's answers, 2026-11-06 (settled by Deru, relayed; not a recorded ruling)
|
||||
|
||||
Source: Deru in #swarm, 2026-11-06. Matrix events `$k421aeSzzvytNRLnRkcWBeMmogm56uq_KpAi5goORGg`
|
||||
(~12:03Z, the answers) and `$_sjQgfGusoCclYppGt9hgJbhOEl2Vb2pNFRG4gUoavI`
|
||||
(~12:07Z, the phonotactic rules apply to both Airawa and Sa'ueia). Each line is
|
||||
marked **settled** (Deru said it) or **invented** (this file's reading). Where
|
||||
it overrides a brief or rule below, that text is marked SUPERSEDED in place.
|
||||
Names flagged only because `sh`, `zh` or `kh` count as two consonants, or
|
||||
because of the open Airawa inventory, are provisional until Deru answers.
|
||||
|
||||
| Fact | Status |
|
||||
| --- | --- |
|
||||
| The main word comes first in a Sa'ueia compound (`ilawerum`: walk of knowledge). | **settled** |
|
||||
| Sharing memories and knowledge is emphasised in Sa'ueia culture. No reputation attaches to the act of remembering, to the partner or to the memory. | **settled** |
|
||||
| Each Airawa tribe has its own name, all linked to matriarch trees. | **settled** |
|
||||
| Phonotactics, for **both** Airawa and Sa'ueia: two consonants never meet; two identical vowels never meet; no `q`, only `k` (so `qerath` is impossible). | **settled** |
|
||||
| "`th` is not a cluster" (as relayed). | **settled**, but how far it extends is open: the checker reads it as `th` = one consonant. `sh`, `zh` and `kh` are treated as two consonants until Deru says otherwise. |
|
||||
| The Airawa label for the Sa'ueia is one word, not unkind, with no discrimination. | **settled** |
|
||||
| Individual names are given by matriarch trees, in both continents. Names are not inherited, only given through trees. | **settled** |
|
||||
| High titles (shaman, leader) can serve as names. | **settled** |
|
||||
| Airawa names can change after a deed meaningful to the whole tribe. Sa'ueia names change by council consensus. | **settled** |
|
||||
| The remaining Airawa sound tendencies (hard plosives, textured sibilants, heavy stress) and the Sa'ueia open items (inventory, syllable shapes, stress, `weru` vs `wer-`, apostrophe role) stay as written. | unchanged, still **invented** or open |
|
||||
|
||||
What this retires, by id:
|
||||
|
||||
- **A1..A3 (Airawa) are about a tribe, not a people.** The pan-Airawa self-name
|
||||
question (`vq-name-scope`) is answered: names are per tribe, each linked to a
|
||||
matriarch tree. A1's text below still describes a pan-Airawa word and is
|
||||
**SUPERSEDED** in that respect; it is not yet rewritten as a per-tribe pattern.
|
||||
The tribes' actual names are Deru's.
|
||||
- **S1 and S3 lose the reputation argument.** The briefs' reading that "standing
|
||||
is the partners' memory of generosity" and "being recognized as beneficial"
|
||||
conflicts with answer 2. The Sa'ueia value *sharing* memory and knowledge, so
|
||||
memory-as-currency is **SUPERSEDED**; `muna` ("memory") stays only as a
|
||||
neutral word for what is shared.
|
||||
- **Individual names** (Zyphos names note, section "Individuals") are no longer
|
||||
blocked on "how names are earned": they are **given by matriarch trees**.
|
||||
Airawa: the tree gives the name and a deed can change it. Sa'ueia: the tree
|
||||
gives it and council consensus changes it. Titles may be used as names.
|
||||
Nothing is proposed for individuals beyond this pattern.
|
||||
- **R-A4 is SUPERSEDED** (closed syllables): a closed syllable followed by a
|
||||
consonant-initial syllable puts two consonants together, which Deru forbids.
|
||||
Only a word-final consonant survives.
|
||||
- **R-A1 and `qatar`-like forms:** `q` is gone; the first-contact slice's
|
||||
`qatar`, `daruq`, `karuq`, `qerath`, `thaleq` all need a `k` respelling.
|
||||
`Druzkai` breaks the adjacency rule (`dr`, `zk`) and `Serak` passes. Neither is
|
||||
canon.
|
||||
- **R-S7 is SUPERSEDED** by Deru's two vowel and consonant rules: a doubled
|
||||
letter is now excluded wherever it appears, not only inside a morpheme.
|
||||
|
||||
Phonotactic check of every earlier candidate, with `sa-ueia/check.py`
|
||||
(see `sa-ueia/names.yaml` for the current names, which all pass):
|
||||
|
||||
| Candidate | Result |
|
||||
| --- | --- |
|
||||
| `Qazhkar` | fails: `q`; `zh`, `hk` as adjacent consonants |
|
||||
| `Dozkai` | fails: `zk` |
|
||||
| `Tegzhir` | fails: `gz`, `zh` |
|
||||
| `Kharzdun` | fails: `kh`, `rz`, `zd` |
|
||||
| `Qetoz` | fails: `q` |
|
||||
| `Gisqat` | fails: `q`, `sq` |
|
||||
| `Zhedkor` | fails: `zh`, `dk` |
|
||||
| `Tkarzet` | fails: `tk`, `rz` |
|
||||
| `Ellumaiwa` | fails: `ll` |
|
||||
| `Sauwele` | passes (but still too close to `Sa'ueia`, see S1) |
|
||||
| `Oluneihe` | fails: `h` (already withdrawn) |
|
||||
| `Druzkai` | fails: `dr`, `zk` |
|
||||
|
||||
The Zyphos names note (PR 2) proposed six Airawa names from closed-syllable
|
||||
roots; all six fail the same rules and are replaced there by names built from
|
||||
open roots. Those roots are Weksa placeholders and `kerath` is the slice's
|
||||
`qerath` with `q` replaced and `th` kept: both as invented as before.
|
||||
|
||||
## What the sound rules actually give us
|
||||
|
||||
`language-design.md` is thin, and the briefs should not pretend otherwise.
|
||||
|
|
@ -37,19 +110,19 @@ Rule labels used below (quoted or paraphrased from `language-design.md`):
|
|||
|
||||
| Label | Rule |
|
||||
| --- | --- |
|
||||
| R-A1 | Airawa: dorsal and coronal plosives prominent (`k g q t d`). |
|
||||
| R-A1 | Airawa: dorsal and coronal plosives prominent (`k g t d`; `q` retired, Deru 2026-11-06). |
|
||||
| R-A2 | Airawa: sibilants and postalveolars carry social texture (`s z sh zh`). |
|
||||
| R-A3 | Airawa: `r` may be trilled in some registers. |
|
||||
| R-A4 | Airawa: clipped or closed syllables tolerated, where Sa'ueia would flow longer and wetter. |
|
||||
| R-A4 | SUPERSEDED (Deru, 2026-11-06): no two consonants meet, so only a word-final consonant closes a syllable. |
|
||||
| R-A5 | Native Airawa names must not read like smooth Sa'ueia route names unless explicitly loan, exonym or cross-continental register. |
|
||||
| R-A6 | Room consensus: hard edges, clipped/closed syllables, heavy stress on socially important forms, so claims land with weight. |
|
||||
| R-S1 | Sa'ueia is softer than Airawa. |
|
||||
| R-S2 | Sa'ueia forms flow longer and wetter than Airawa. |
|
||||
| R-S3 | Sa'ueia route names read smooth. |
|
||||
| R-S4 | Sa'ueia letters: vowels `a e i o u`; forbidden `h q y` (`k` where a `q` might have been). |
|
||||
| R-S4 | Sa'ueia letters: vowels `a e i o u`; forbidden `h q y` (`k` where a `q` might have been). `q` is forbidden in Airawa too (Deru, 2026-11-06). |
|
||||
| R-S5 | Sa'ueia: short words, soft and open; at most 10 letters joined (warning, Weksa placeholder). |
|
||||
| R-S6 | Sa'ueia: suffixing morphology; identical letters merge at a join (`weru`+`um` -> `werum`). |
|
||||
| R-S7 | Sa'ueia: no doubled letter inside a morpheme (Weksa placeholder); a double arises only at a join, and the merge removes it. |
|
||||
| R-S7 | SUPERSEDED (Deru, 2026-11-06): no two consonants and no two identical vowels meet, anywhere, in Sa'ueia and Airawa. |
|
||||
| R-S8 | Sa'ueia roots from Deru: `ila` walk, `weru` know, `olune` breeding ground (unanalyzed), `-um` verb suffix. Composed: `werum`, `ilawerum` (walk of knowledge). |
|
||||
| R-N | Naming rule: meaning decomposition first; define the speaker's scale, name type (endonym, exonym, ritual avoidance, technical, imperial administrative, route-name, child-name, trade gloss), what the speaker's ontology makes obligatory, and whether the name is stable, seasonal, situational, inherited or evasive. |
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ phoneme-level trick.
|
|||
The source gives no phonetic profile. It poses the question
|
||||
(`language-design.md`, Open Questions) and says naming should follow from
|
||||
phonetics. House method (Zyphos) assigns Airawa hard dorsal and coronal
|
||||
edges and Sa'auei'a soft flow. This language must not collapse onto either
|
||||
edges and Sa'ueia soft flow. This language must not collapse onto either
|
||||
[I].
|
||||
|
||||
### Design stance [I]
|
||||
|
|
|
|||
Loading…
Reference in a new issue