diff options
Diffstat (limited to 'lib/rites')
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.ml | 161 | ||||
| -rw-r--r-- | lib/rites/rite_ef/precedence_ef.mli | 20 |
2 files changed, 175 insertions, 6 deletions
diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml index 374e9a4..c488596 100644 --- a/lib/rites/rite_ef/precedence_ef.ml +++ b/lib/rites/rite_ef/precedence_ef.ml @@ -1039,6 +1039,145 @@ let rec take n = function still the pair, not the triple; only the INPUT carries [band]. *) let drop_band (c, p, (_ : int)) = (c, p) +(* RG 110 (docs/research/rules-register.md §4, Caput XIV, "De dierum + liturgicorum occurrentia perpetua"), full text, both photographic scans, + word for word: "110. In Officio et Missa S. Petri semper fit + commemoratio S. Pauli, et vicissim. Haec commemoratio dicitur + inseparabilis; et duae orationes adeo in unam coalescere censentur ut, in + numero orationum computando, pro unica habeantur. Proinde: a) in Officio + S. Petri aut S. Pauli, oratio alterius Apostoli additur, ad Laudes et ad + Vesperas, sub unica conclusione, orationi diei, absque antiphona et + versu; b) in Missa S. Petri aut S. Pauli, oratio alterius Apostoli + additur, sub unica conclusione, orationi diei; c) quoties vero oratio + unius Apostoli addenda est ad modum commemorationis, huic orationi + additur altera immediate, ante omnes alias commemorationes." -- in the + Office and Mass of St Peter, a commemoration of St Paul is ALWAYS made, + and vice versa. This commemoration is called INSEPARABLE, and the two + orations are held to coalesce into one so much that, in counting the + number of orations [RG 111's own admission cap], they are counted AS + ONE. Accordingly: (a)/(b) in the Office/Mass OF Peter or Paul [i.e. when + one of them is the day's own observed office], the other Apostle's + oration is added, under a single conclusion, to the oration of the day; + (c) whenever the oration of ONE Apostle must be added AS A + COMMEMORATION [i.e. when one of them is not the day's own office but is + itself only being admitted as an ordinary/privileged commemoration + elsewhere], the OTHER is added to it immediately, BEFORE ALL OTHER + COMMEMORATIONS. + + Three real pairs in this codebase's data, all three confirmed on both + photographic scans (register §4): 25 January's [conversion-of-st-paul] + + [peter]; 22 February's [chair-of-st-peter] + [paul]; 30 June's + [in-commemoratione-sancti-pauli-apostoli] + [commemoration-of-st-peter] + (data/ef/adjustments.sexp's own `Add` directive -- lectio has no + equivalent entry at all, a genuine upstream data gap, not merely a + colitur bootstrap miss). 29 June ([sts-peter-paul]) needs none: it is + already the JOINT feast, not one Apostle's office alone. [peter]/[paul] + already existed as ordinary [Commemoration_only] sanctoral candidates + before this task (Task 10's bootstrap) -- part of RG 110's own machinery, + just wired into the ORDINARY, CAPPED RG 111 admission contest instead of + this rule's own UNCAPPED one, which is the defect this closes: measured + (1583-9999, `tools/`'s own throwaway scan, reproduced and discarded per + this task's own report) at 3,533 years where [chair-of-st-peter] is + observed but [paul] loses the day's single non-Sunday-II-class slot to a + competing privileged Advent/Lent/Ember feria (RG 109(e)/(d)), and a + FURTHER 593 years of shape (c) below, where [chair-of-st-peter] itself + loses outright to an ordinary (ordinary, i.e. non-I-class) II-class + Sunday and is admitted only AS a commemoration -- [paul] was entirely + absent in every one of those 593 years too, before this fix. 25 + January/30 June never show shape (c) at all: both trigger candidates are + [Class3], below RG 111(b)'s "de festo II classis" floor, so neither can + ever be admitted as a mere ordinary commemoration on a II-class Sunday + in the first place -- confirmed by the same scan, 0 instances either + way, not assumed absent. + + [rg110_companion_slug] reads ONLY the candidate's own slug -- the same + convention every other not-an-RG-citation lookup in this file uses + ({!universal_layer}, {!vigil_suffix}, {!nativity_octave_prefix}) -- not + [subject]/[rank]/anything else, because "which slug pairs with which" is + RG 110's own closed, three-pair list, not a general property [band]/ + [disposition] could derive structurally the way (say) [is_sunday_slug] + derives Sunday-ness from a naming convention. A rename of any of the six + slugs involved (either side of a pair) has nowhere else to be caught but + here -- flagged the same way {!annunciation_slug} already is. *) +let rg110_companion_slug (slug : string) : string option = + if slug = "chair-of-st-peter" then Some "paul" + else if slug = "conversion-of-st-paul" then Some "peter" + else if slug = "in-commemoratione-sancti-pauli-apostoli" then Some "commemoration-of-st-peter" + else None + +(* Looks [companion_slug] up directly in [comms] -- the SAME full candidate + pool {!admit} itself sorts from, before any cap is applied, so this finds + the companion regardless of whether the NORMAL admission process would + have kept it (it never would: {!band} always ranks a [Commemoration_only] + candidate at {!unclassified}, worse than literally any real table entry, + so a companion competing on its own merits never survives a contested + slot -- RG 110's entire point is that it should not have to). Returns the + ORIGINAL triple's candidate/privilege pair, unrebuilt, for the same + physical-equality reason {!drop_band} and this function's own top comment + already document -- reusing the [privilege] {!disposition}/{!privilege_of} + already computed for it rather than inventing a new tag here. *) +let rg110_find_companion comms companion_slug = + List.find_map + (fun (c, p, (_ : int)) -> + if Slug.to_string c.Precedence.cel.Celebration.slug = companion_slug then Some (c, p) else None) + comms + +(* RG 110's own uncapped addition, applied to [normal] -- the day's ALREADY- + COMPUTED, ORDINARILY-CAPPED admission result (every branch of {!admit} + below, unchanged otherwise). Two triggers, covering RG 110(a)/(b) and + RG 110(c) respectively, in this function's own comment above: + - [observed] itself is one of the three trigger slugs (shape (a)/(b): + Peter's or Paul's own Office/Mass IS today's day) -- the companion is + looked up directly and appended, uncapped. + - one of [normal]'s OWN members is a trigger slug (shape (c): Peter's or + Paul's own office lost the day outright but is itself being admitted + as an ordinary/privileged commemoration of whatever else won) -- same + lookup, same append. + Both can never fire together on one call ({!Precedence.resolve} only + ever calls {!Precedence.rules.admit} once per date, and all three + trigger slugs are distinct FIXED calendar dates -- 25 January, 22 + February, 30 June -- so at most one of the two conditions is ever true + for a given [observed]/[normal] pair; [List.filter_map] over both + sources together is simply the shortest way to express "check every + place a trigger slug could legitimately appear", not evidence they are + expected to co-occur). + + PREPENDED, not appended, per RG 110(c)'s own explicit ordering directive + ("ante omnes alias commemorationes") -- the one clause in this rule that + actually specifies WHERE, restated here for shape (a)/(b) too rather than + drawing an unwarranted distinction between the two: no live data in this + codebase ever has a THIRD, unrelated commemoration alongside a RG 110 + pair to make the ordering choice visible in real output (the companion is + always the only OTHER item, or the only item, in [normal] already), so + this is closer to a defensive convention than a tested outcome -- flagged + honestly rather than claimed as independently verified. + + A companion already present in [normal] on its own merits (structurally + unreachable today -- {!unclassified}'s own comment above -- but not + provably so for every future rite/data shape) is not added a second + time: [List.exists] guards each append. *) +let rg110_additions (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege * int) list) + ~(observed : Vocab_ef.rank Precedence.candidate) + (normal : (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list) : + (Vocab_ef.rank Precedence.candidate * Precedence.privilege) list = + let already_has slug = + List.exists (fun (c, _) -> Slug.to_string c.Precedence.cel.Celebration.slug = slug) normal + in + let trigger_slugs = + Slug.to_string observed.Precedence.cel.Celebration.slug + :: List.map (fun (c, _) -> Slug.to_string c.Precedence.cel.Celebration.slug) normal + in + let companions = + List.filter_map + (fun trigger_slug -> + match rg110_companion_slug trigger_slug with + | None -> None + | Some companion_slug -> + if already_has companion_slug then None else rg110_find_companion comms companion_slug) + trigger_slugs + in + companions @ normal + let admit ~(observed : Vocab_ef.rank Precedence.candidate) ~(temporal : Vocab_ef.rank Precedence.candidate) (comms : (Vocab_ef.rank Precedence.candidate * Precedence.privilege * int) list) : @@ -1073,8 +1212,16 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate) is_sunday_slug (Slug.to_string temporal.Precedence.cel.Celebration.slug) in let open Vocab_ef in - match (observed_rank, day_is_sunday) with - | Class1, _ -> + (* RG 110's own uncapped additions are layered on AFTER this match -- + {!rg110_additions}'s own top comment -- never inside any one branch: + the three trigger slugs can each reach [observed] via any rank/Sunday + combination the table admits (a Class2 fixed feast, here, but nothing + in {!rg110_additions} itself assumes that), so computing it once, + uniformly, over whatever this match already decided is both simpler + and safer than duplicating the same lookup into every branch. *) + let normal = + match (observed_rank, day_is_sunday) with + | Class1, _ -> (* RG 111: "I class: none save one privileged." Ordinary commemorations never get a slot at all on a I-class day, no matter how many are due; at most one privileged one does, the highest-precedence one @@ -1127,10 +1274,12 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate) goes to whichever candidate outranks the rest by RG 113's own table-of-precedence order ({!band}), privileged or not. *) (match sorted with [] -> [] | best :: _ -> [ drop_band best ]) - | (Class3 | Class4), _ -> - (* RG 111: "III-IV class: at most two" -- by RG 113's table order, same - as the non-Sunday II-class case, just with room for two. *) - List.map drop_band (take 2 sorted) + | (Class3 | Class4), _ -> + (* RG 111: "III-IV class: at most two" -- by RG 113's table order, same + as the non-Sunday II-class case, just with room for two. *) + List.map drop_band (take 2 sorted) + in + rg110_additions comms ~observed normal (* Task 11: RG 96 -- where an impeded I-class feast lands (docs/research/ rules-register.md §4, "Transfer/translation"). [band] decides who is diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli index 6a96c4e..765b573 100644 --- a/lib/rites/rite_ef/precedence_ef.mli +++ b/lib/rites/rite_ef/precedence_ef.mli @@ -243,6 +243,26 @@ val september_ember_prefix : string - [observed] a [Class3] or [Class4] day: at most two, by {!band}'s table order alone. + ADDED, ef-holyname-rg110 task: RG 110 (docs/research/rules-register.md + §4, Caput XIV) -- "In Officio et Missa S. Petri semper fit + commemoratio S. Pauli, et vicissim... pro unica habeantur" -- whenever + [observed], or one of the candidates the FOUR cases above already admit, + is one of RG 110's own three trigger slugs (25 January's + [conversion-of-st-paul], 22 February's [chair-of-st-peter], 30 June's + [in-commemoratione-sancti-pauli-apostoli]), its companion Apostle + ([peter]/[paul]/[commemoration-of-st-peter] respectively) is looked up + directly in [comms] and PREPENDED to whatever the four cases above + already produced -- UNCAPPED, i.e. added on top of, not competing for, + any of those cases' own admission limits ("pro unica habeantur": the + pair is held as ONE for RG 111's own counting purposes, so it never + consumes a second ordinary/privileged slot). This is layered on AFTER + the four cases above decide the day's ordinarily-capped result, uniformly + regardless of which of the four produced it -- see the .ml's own + [rg110_additions] for the full citation, the measured blast radius, and + why the two lookups (observed itself, and each already-admitted + candidate) together cover both of RG 110's own textual triggers, + (a)/(b) and (c). + CORRECTED, Task B (branch ef-rg16a): this used to sort by [Vocab_ef.rank] (RG 8's coarse four-class "dignity") and break same-rank ties alphabetically by slug -- an engineering convention with no |
