Module talk:Auphen: Difference between revisions
Drop the kept-behaviour clause that D15 contradicts (flag 49) |
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. | |||
— [[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?nstarts at char n. - D4 — positive-index affix insertion/deletion is not off by one.
- D5 — circumfix
A#Bwraps 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]holdstʃ, 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.