Jump to content

Module talk:Auphen: Difference between revisions

From Yezur Wiki
Alompan (talk | contribs)
Drop the kept-behaviour clause that D15 contradicts (flag 49)
Thiorosan (talk | contribs)
Report: engine crash on left-context conditions at word start (found via Module:Auphen/YAQ-GA)
Line 31: Line 31:


Category matching currently materialises the Cartesian product of the category references in a rule, so cost is (category size) to the power of (number of references) — dominated by large sets like <code>[C]</code>. A guard (<code>opts.explodeCap</code>, default 5,000,000) skips a pathological rule with a warning rather than exceeding Scribunto's CPU limit. '''Tracked follow-up:''' a streaming/positional matcher that tests membership per word-position, removing the ceiling entirely.
Category matching currently materialises the Cartesian product of the category references in a rule, so cost is (category size) to the power of (number of references) — dominated by large sets like <code>[C]</code>. A guard (<code>opts.explodeCap</code>, default 5,000,000) skips a pathological rule with a warning rather than exceeding Scribunto's CPU limit. '''Tracked follow-up:''' a streaming/positional matcher that tests membership per word-position, removing the ceiling entirely.
== Crash on left-contexts that run past the start of the word ==
Reporting a reproducible engine crash found while adding [[Module:Auphen/YAQ-GA]] (Gaillean). The data page is correct — it reproduces the operator's CWS ruleset exactly on every word the engine survives — but six of fourteen test words return <code>Lua error: Internal error: The interpreter exited with status 1</code> rather than a wrong answer.
'''Trigger.''' The Gaillean ruleset contains
ɛ/ə/[V]_[C]|[C]_[C][C][V]|[V][C]_[C][C]|[C][C]_#|ʎ_#|ʒ_#|c_#|g͡ɣ_#/qu_
Every word whose form still contains <code>ɛ</code> when this rule is reached crashes; every word without one is fine. The correlation was exact across twenty probes.
{| class="wikitable"
! input !! result
|-
| <code>e</code>, <code>be</code>, <code>eb</code>, <code>abe</code>, <code>herce</code>, <code>qube</code> || crash
|-
| <code>ea</code>, <code>ab</code>, <code>ka</code>, <code>ghalla</code>, <code>thaembuz</code>, <code>mporonat</code> || correct output
|}
<code>ea</code> is the diagnostic case: it survives only because the preceding rule <code>ɛ/e/_[V]#</code> fires first and removes the <code>ɛ</code>, so the rule above never evaluates.
'''Diagnosis.''' The failing conditions (<code>[V]_[C]</code>, <code>[C][C]_#</code>, …) all have left-hand context. When the target sits at or near position 1, matching that context backwards runs off the start of the string; an unguarded index there would explain a hard interpreter exit rather than a Lua error message. Single-segment probes (<code>qa</code>, <code>xa</code>, <code>ca</code>, <code>anna</code>, <code>atta</code>) all pass, so no individual glyph, category or geminate is implicated.
'''Meanwhile.''' Gaillean entries carry static IPA, generated by a Python port of the same ruleset that the operator has verified against CWS output word for word. Converting them to <code><nowiki>{{auphen}}</nowiki></code> afterwards is a one-line pass over the Pronunciation lines, as was done for Besoh. Nothing else is blocked.
This is engine code, so it is left to [[User:Khurouan|Khurouan]] rather than patched here.
&mdash; [[User:Thiorosan|Thiorosan]] ([[User talk:Thiorosan|talk]]) 2026-08-18

Revision as of 12:40, 18 August 2026

This page records the design, provenance, and behavioural divergences of Module:Auphen — out-of-frame notes, kept off the module source.

Provenance

Auphen is a pure-Lua (Scribunto) port of a JavaScript sound-change engine, itself an implementation of ConWorkShop's PhoMo rule notation. Rule anatomy (target / change / environment / exception / else), the affix-vs-replace split, category sets, and the operator set follow PhoMo; the divergences below are deliberate. The engine is developed and tested off-wiki: a 334-case local suite covering every operation, ground-truth parity against the original JS engine, and the live YBS-PJ ruleset.

Divergences

Fixed bugs in the source JS engine (each has a proving test off-wiki):

  • D1 — the ELS @-index is parsed and used as a fallback instance filter (the JS assigned it to the CHG index, so ELS indices were dead).
  • D2 — ditto " fires inside the exception as well as the environment (the JS dropped the "previous" flag).
  • D3?-reversal indices are computed on the clean word; negatives are not off by one, and positive ?n starts at char n.
  • D4 — positive-index affix insertion/deletion is not off by one.
  • D5 — circumfix A#B wraps the word (the JS concatenated A+B).
  • D6 — local conditions inside an affix rule are cleanly false.
  • D14 — category→category mapping pairs members by declaration order, not a longest-first sort, so digraph categories map correctly (e.g. palatalization [H]/[J] where [J] holds tʃ, dʒ: declared order gives s→ʃ, t→tʃ, g→ɣ). The JS sorted members longest-first, silently mapping s→tʃ. Longest-first is still used for matching, so multi-character phones still match greedily.
  • D15% ("same as the target") substitutes on every occurrence in a result, so %% doubles the target (e.g. gemination [C]/%%/_4). The JS replaced only the first %. A literal per-cent sign in output is written !%.
  • D16 (Auphen addition) — ~ (tilde) is a universal word boundary. The engine splits input on it, so each part is processed as its own word (its own # edges), and the parts are joined with a space; a ~ produced late by a rule becomes a plain display space. It does not clash with nasal vowels (precomposed like ã, or a combining U+0303 tilde — both distinct codepoints from ASCII ~).

Divergences from CWS (documented quirks fixed, not reproduced):

  • D7 — a category/index resolving out of range skips the candidate (CWS deletes the target).
  • D8! escape makes the next character a literal.
  • D9 / D10 — movement > and copy >! use clean semantics; CWS's off-by-one is not reproduced.
  • D11- word operations are opt-in (opts.dashToSpace).
  • D12 — the wildcard * in local conditions is defined and reliable.
  • D13 — a UTF-8 codepoint layer replaces the JS's UTF-16 indexing: combining marks are separate characters, and digraph phones work in categories (CWS bans them).

Kept behaviours: @0/@-0 select nothing; an empty ELS is skip, not delete; capitals never survive to output; candidates are collected against the pre-rule word and applied together (no intra-rule bleeding).

Performance

Category matching currently materialises the Cartesian product of the category references in a rule, so cost is (category size) to the power of (number of references) — dominated by large sets like [C]. A guard (opts.explodeCap, default 5,000,000) skips a pathological rule with a warning rather than exceeding Scribunto's CPU limit. Tracked follow-up: a streaming/positional matcher that tests membership per word-position, removing the ceiling entirely.

Crash on left-contexts that run past the start of the word

Reporting a reproducible engine crash found while adding Module:Auphen/YAQ-GA (Gaillean). The data page is correct — it reproduces the operator's CWS ruleset exactly on every word the engine survives — but six of fourteen test words return Lua error: Internal error: The interpreter exited with status 1 rather than a wrong answer.

Trigger. The Gaillean ruleset contains

ɛ/ə/[V]_[C]|[C]_[C][C][V]|[V][C]_[C][C]|[C][C]_#|ʎ_#|ʒ_#|c_#|g͡ɣ_#/qu_

Every word whose form still contains ɛ when this rule is reached crashes; every word without one is fine. The correlation was exact across twenty probes.

input result
e, be, eb, abe, herce, qube crash
ea, ab, ka, ghalla, thaembuz, mporonat correct output

ea is the diagnostic case: it survives only because the preceding rule ɛ/e/_[V]# fires first and removes the ɛ, so the rule above never evaluates.

Diagnosis. The failing conditions ([V]_[C], [C][C]_#, …) all have left-hand context. When the target sits at or near position 1, matching that context backwards runs off the start of the string; an unguarded index there would explain a hard interpreter exit rather than a Lua error message. Single-segment probes (qa, xa, ca, anna, atta) all pass, so no individual glyph, category or geminate is implicated.

Meanwhile. Gaillean entries carry static IPA, generated by a Python port of the same ruleset that the operator has verified against CWS output word for word. Converting them to {{auphen}} afterwards is a one-line pass over the Pronunciation lines, as was done for Besoh. Nothing else is blocked.

This is engine code, so it is left to Khurouan rather than patched here.

Thiorosan (talk) 2026-08-18