diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-11 22:46:30 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-11 22:46:30 +0200 |
| commit | 07c87370d6a0de687b42a41d967135578f014bdc (patch) | |
| tree | 841b29952515e9fd662bc46c0b8457303fb2300d /lib/rites/rite_ef/precedence_ef.ml | |
| parent | 553dc44d2ba0e131e7f2ac79dc755641afcd6a1c (diff) | |
| download | colitur-07c87370d6a0de687b42a41d967135578f014bdc.tar.gz colitur-07c87370d6a0de687b42a41d967135578f014bdc.zip | |
rite(ef): occurrence dispositions (RG 92-95, 33, 94)
Precedence_ef.disposition decides the loser's fate in an occurrence: a
Commemoration_only celebration is always commemorated (it can never win or
transfer); a I- or II-class vigil impeded by any Sunday or a I-class feast
is entirely omitted (RG 33), checked before the generic rule below or the
Nativity/Pentecost Vigil could wrongly transfer; any other I-class loser
transfers (RG 95 -- only I class has the right of translation); everything
else is commemorated, with the admit-or-omit decision left to RG 108-111's
admission count (Task 9). RG 94 needs no code: resolve always compares a
loser against the day's actual winner, never against a departed sibling, so
no commemoration can ride along with a transferred feast in this design.
This is the branch that completes Task 7's carried All Souls fix: once it
loses to an occurring Sunday, its untouched Class1 rank routes it to
Transfer via the generic rule, not a special case. Landing on 3 November is
Rite.transfer_target's job, not wired up yet.
Commemorate carries an interim Precedence.Ordinary privilege pending Task
9's RG 109 implementation, exposed as interim_privilege for that task to
replace.
Table-driven tests cover each rule, including RG 33's boundary from both
sides and a Commemoration_only loser that is also Class1 and vigil-shaped
to pin the branch ordering. Mutation-tested: disabling RG 33, either
direction of RG 95's rank condition, or the Commemoration_only priority
check each fail exactly the rows built to catch them.
Diffstat (limited to 'lib/rites/rite_ef/precedence_ef.ml')
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.ml | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml index 0716177..d8d64a8 100644 --- a/lib/rites/rite_ef/precedence_ef.ml +++ b/lib/rites/rite_ef/precedence_ef.ml @@ -214,3 +214,98 @@ let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedenc "feria" that is also a vigil is not a feria RG 91 describes. *) else if (not is_vigil) && rank = Class4 then 28 else unclassified + +(* Task 8: what happens to the day's LOSING candidate (docs/research/ + rules-register.md ยง4, "Occurrence" RG 92-95 and "Vigils" RG 33, plus RG + 94). [band] above decides who wins; this decides the loser's fate, which + turns on the LOSER's own rank and status (RG 95), except RG 33's vigil + omission, which also has to read the winner. Nothing here ever returns + [Precedence.Repose]: that disposition denotes RG 100-102's *repositio* + (perpetual impediment from a proper/diocesan calendar), out of this + plan's scope -- see calendar.mli's own note that nothing in the EF + ruleset currently emits it. *) + +(* RG 33 (register line 383-384): a I- or II-class vigil falling on any + Sunday or a I-class feast is entirely omitted. Every Sunday slug this + rite's temporal cycle produces -- named (temporal_ef.ml's [named], e.g. + "ef-easter-sunday") or the generic "ef-<season>-sunday-<n>" fallback + ([sunday_slug]) -- contains this marker; nothing else [band] classifies + does. Not an RG citation itself -- see [universal_layer]'s note on this + file's own naming conventions -- exposed for the same reason as + {!vigil_suffix}: a future rename of temporal_ef's Sunday-slug format has + somewhere to be caught other than a silently-wrong RG 33 disposition. *) +let sunday_marker = "-sunday" + +let contains_substring s ~needle = + let ls = String.length s and ln = String.length needle in + let rec at i = i + ln <= ls && (String.sub s i ln = needle || at (i + 1)) in + ln = 0 || at 0 + +let is_sunday_slug slug = contains_substring slug ~needle:sunday_marker + +(* RG 33's "any Sunday or a I-class feast": every RG 91 entry that can ever + outrank a II-class vigil (entry 21) without itself being a Sunday is I + class by the table's own structure (entries 1-13), so [rank = Class1] + alone already covers every way a I-class vigil (entries 5, 9 -- Nativity, + Pentecost) can be impeded at all; the slug check is what a II-class vigil + impeded by an ordinary Sunday (entry 15, rank Class2) needs, since that + winner's own rank is not Class1. *) +let impedes_vigil (winner : Vocab_ef.rank Precedence.candidate) = + let cel = winner.Precedence.cel in + cel.Celebration.rank = Vocab_ef.Class1 + || is_sunday_slug (Slug.to_string cel.Celebration.slug) + +(* Not yet RG 109 (Task 9's job: the closed list of privileged commemorations + and RG 108-111's admission counts). Every [Commemorate] this function + returns carries this one placeholder rather than a silent default, so the + choice is visible and grep-able. [Ordinary] chosen over [Privileged] + deliberately: it grants no admission entitlement RG 111 has not earned, + so code that trusts this value before Task 9 replaces it under-privileges + a commemoration rather than over-privileges one -- the safer direction to + be wrong in. *) +let interim_privilege = Precedence.Ordinary + +let disposition ~(winner : Vocab_ef.rank Precedence.candidate) + ~(loser : Vocab_ef.rank Precedence.candidate) : Precedence.disposition = + let open Vocab_ef in + let cel = loser.Precedence.cel in + if cel.Celebration.status = Celebration.Commemoration_only then + (* Always -- checked before RG 33's omission and RG 95's transfer so + neither can override it: a Commemoration_only entry can never win + (Precedence.resolve holds it out of the band contest entirely, see + that module's [resolve]) and, per the brief, can never transfer + either. *) + Precedence.Commemorate interim_privilege + else if + (cel.Celebration.rank = Class1 || cel.Celebration.rank = Class2) + && is_vigil (Slug.to_string cel.Celebration.slug) + && impedes_vigil winner + then + (* RG 33. Checked before the generic Class1 -> Transfer rule below, or a + I-class vigil (Nativity, Pentecost) impeded on its own Sunday/ + I-class-feast terms would wrongly transfer instead of vanishing. *) + Precedence.Omit + else if cel.Celebration.rank = Class1 then + (* RG 95: only I-class feasts have the right of translation. This is the + branch that completes Task 7's All Souls fix (register line 334, RG + 91 entry 8): All Souls is I class and not a vigil, so once it loses + to an occurring Sunday it reaches here and transfers -- to 3 + November per the register, but WHERE it lands is + Rite.transfer_target's job (RG 96), not this function's; disposition + only says THAT it moves. *) + Precedence.Transfer + else + (* RG 95's other branch, for everything below I class: "aut + commemorantur aut penitus omittuntur" -- commemorated or wholly + omitted. Which of the two survives is RG 108-111's admission count + (Task 9's [admit]), not this function's decision; this only opens the + commemoration. + + RG 94 (a fixed-day commemoration is not carried along with a + transferred feast) needs no code here: [Precedence.resolve] calls + this function once per loser, always against the day's actual + [observed] winner -- never against a fellow loser that itself + transferred away -- so no mechanism exists by which a commemoration + could ride along with a departing feast in the first place; there is + nothing to suppress. *) + Precedence.Commemorate interim_privilege |
