(** The OF reading-cycle arithmetic and the temporal + sanctoral lectionary resolution chain (Task 4, 2026-08-25-colitur-of-phases-3-5). Two halves with a different character, per the task's own brief: - {b The cycle rules are CODE}, computed from the civil year, cited to the OLM (Ordo Lectionum Missae) 1981 Praenotanda -- see {!sunday_cycle} and {!weekday_cycle} below for the citations, each verified against the real page image (docs/research/of/olm-1981.pdf pp.32-33/"XXXII-XXXIII"), not the unreliable OCR text layer (design spec 2026-08-24-colitur-of- rite-module-design.md sec4.4's own caveat). - {b The data is bootstrapped from lectio} (binding decision 3), a POLISH VERNACULAR pastoral lectionary (niedziela.pl), not the Latin OLM itself -- see tools/bootstrap_lectionary_of.ml's own top-of-file comment and data/of/lectionary.sexp's own provenance header for the full lineage statement and what it cannot show. *) open Colitur_kernel (** OLM 1981 Praenotanda n.66 (page-image verified, p.32/"XXXII"): "cyclus trium annorum proponitur" -- the three-year Sunday/solemnity cycle. Footnote 102, same page, gives the determining rule with a worked example: "Per litteram C designatur annus cuius numerus in tres partes aequales dividi potest... Sic annus 1980 est annus C, annus vero sequens, scilicet annus 1981, est annus A, annus vero 1982 est annus B, et annus 1983 est iterum annus C" -- the year divisible by three is Year C, the next Year A, the next Year B. The SAME footnote also gives the Gospel-per-year identity quoted on {!sunday_cycle}'s own doc comment below (A = Matthew, B = Mark, C = Luke), and states explicitly that each cycle's own "year" runs "iuxta dispositionem anni liturgici, nempe a prima hebdomada Adventus, quae cadit in anno civili praecedente" -- according to the liturgical year, namely from the first week of Advent, WHICH FALLS IN THE PRECEDING CIVIL YEAR. This is the task brief's own "the cycle turns at Advent I, not 1 January": Advent 1980 (civil year 1980) already opens the 1981-labelled liturgical year (Year A), even though most of civil year 1980 was still Year C. *) type sunday_cycle = Year_a | Year_b | Year_c (** OLM 1981 Praenotanda n.69 (page-image verified, p.33/"XXXIII"), the "c) De ordinatione lectionum pro feriis" subsection's own point 4: "Pro feriis vero triginta quattuor hebdomadarum <> lectiones evangelicae unico disponuntur cyclo, qui singulis annis resumitur. Prior vero lectio, in duplici cyclo ordinatur, alternis annis legenda. Annus primus adhibetur annis imparibus, annus secundus vero annis paribus" -- for the ferias of the thirty-four weeks of Ordinary Time, the GOSPEL is arranged in a single cycle repeated every year; the FIRST reading alone alternates over two years. Year I is used in odd years, Year II in even years. The SAME point 4, its own preceding points 2-3, state the two-year alternation is confined to Ordinary Time: Lent has its own single seasonal cycle (point 2) and "Pro feriis Adventus, temporis Nativitatis et paschalis cyclus eodem modo annualis est: lectiones ideo non mutantur" -- for the ferias of Advent, Christmastide and Paschaltide the cycle is likewise annual: the readings DO NOT CHANGE (point 3). {!Lectionary.t} does not need to encode that distinction itself: a non-alternating family's own two cycle-letter keys were collapsed to one flat entry at bootstrap time (see tools/bootstrap_lectionary_of.ml's own [collapse_weekday]), so trying both letters here is always safe -- the flat entry is found first either way. *) type weekday_cycle = Year_i | Year_ii val sunday_cycle_letter : sunday_cycle -> string val weekday_cycle_letter : weekday_cycle -> string (** [sunday_cycle ~year_start date]: which of the three-year cycle's own labelled years [date] falls in, per {!sunday_cycle}'s own citation. A = Matthew, B = Mark, C = Luke -- OLM n.66's own footnote 102, same passage: "primus cycli annus dicitur et est annus legendi Matthaeum, ceteri vero, secundus nempe et tertius, respective annus legendi Marcum et annus legendi Lucam". [year_start] is the rite's own Advent-anchor function ({!Temporal_of.year_start}), supplied by the caller rather than hard-coded, the same seam {!Rite_ef.Lectionary_ef .readings}'s own [~lectionary]/[~commons] already use for rite data -- this module reads no filesystem and knows no OF-specific season vocabulary of its own beyond what [year_start] and {!Colitur_kernel .Temporal.t} already carry. *) val sunday_cycle : year_start:(int -> Date.t) -> Date.t -> sunday_cycle (** [weekday_cycle ~year_start date]: which of the two-year cycle's own labelled years [date] falls in, per {!weekday_cycle}'s own citation. *) val weekday_cycle : year_start:(int -> Date.t) -> Date.t -> weekday_cycle (** The Mass's Epistle and Gospel citations for this day, and (when the shipped data can name one) which formulary they came from -- the OF counterpart of {!Rite_ef.Lectionary_ef.readings}, deliberately simpler: OF has no Commons indirection table (spec's own scope for this task), so the chain is three steps, not four: - {b Step 1} -- the observed celebration's own embedded citations, if any. {!Colitur_kernel.Mass_formulary.Proper}. Defensive: every shipped data/of/calendar-2002.sexp entry carries [citations ()] today (Task 1), so this step is currently dead on shipped data, kept for the same forward-compatibility reason EF's own step 1 stays first even where most callers reach it rarely. - {b Step 2} -- when the observed celebration is NOT itself the day's own temporal office (a real sanctoral entity won occurrence), its own slug looked up directly in [lectionary] (tried flat, then both cycle-letter suffixes via {!sunday_cycle}/{!weekday_cycle} -- a sanctoral entry may be tagged either way in the shipped data, see tools/bootstrap_lectionary_of.ml's own header, so both are tried rather than assumed). Tagged {!Colitur_kernel.Mass_formulary.Proper} too, not {!Colitur_kernel.Mass_formulary.Common}: OF has no shared- formulary indirection the way EF's Commons do (this task's own scope), so a looked-up sanctoral citation is still, substantively, "the observed celebration's own proper" -- only ITS STORAGE differs from step 1's (external file vs an embedded field), which {!Colitur_kernel.Mass_formulary.source} was never designed to distinguish (its own doc names what DECIDED the Mass, not where the bytes happen to live). {!Colitur_kernel.Mass_formulary.source} is a closed kernel type outside this task's own scope to extend; see lectionary_of.ml's own implementation comment on this branch for the full argument. [said] the observed slug. - {b Step 3} -- the day's own temporal slug, same three-way lookup. {!Colitur_kernel.Mass_formulary.Own_slug}, [said] that slug. - A day matching neither gets [(None, [])] -- see data/of/lectionary .sexp's own provenance header for exactly which days that is, both directions, measured, not guessed. NO preceding-Sunday fallback (EF's own step 3): unlike EF's RG 299, the OF's own two-year FERIAL cycle assigns every Ordinary Time weekday its own reading by design (OLM n.69 point 4, {!weekday_cycle}'s own citation) -- there is no OF norm this task found instructing a ferial with no proper of its own to repeat the preceding Sunday's Mass, so no such step is built. [temporal_at] is therefore accepted, unused, to satisfy {!Colitur_kernel.Rite.t}'s own [readings] field shape (the same contract {!Rite_ef.Lectionary_ef.readings} satisfies) -- kept rather than dropped so a future task that DOES find such a rule has the callback already in place. *) val readings : lectionary:Lectionary.t -> year_start:(int -> Date.t) -> observed:Vocab_of.rank Celebration.t -> temporal:(Vocab_of.season, Vocab_of.rank) Temporal.t -> date:Date.t -> temporal_at:(Date.t -> (Vocab_of.season, Vocab_of.rank) Temporal.t) -> Mass_formulary.t option * Citation.t list