Deru phonotactics in the Sa'ueia/Airawa checker; names file; briefs updated #9

Merged
weksa merged 3 commits from weksa-names/deru-phonotactics into main 2026-11-06 19:24:20 +00:00
7 changed files with 353 additions and 31 deletions

View file

@ -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:

View file

@ -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), "

View 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]}

View file

@ -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

View file

@ -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()

View file

@ -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. |

View file

@ -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]