aboutsummaryrefslogtreecommitdiff
path: root/lib/rites/rite_of/precedence_of.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rites/rite_of/precedence_of.ml')
-rw-r--r--lib/rites/rite_of/precedence_of.ml84
1 files changed, 68 insertions, 16 deletions
diff --git a/lib/rites/rite_of/precedence_of.ml b/lib/rites/rite_of/precedence_of.ml
index 0587f0a..5344374 100644
--- a/lib/rites/rite_of/precedence_of.ml
+++ b/lib/rites/rite_of/precedence_of.ml
@@ -300,9 +300,22 @@ let vigil_feast (_ : Vocab_of.rank Precedence.candidate) : Slug.t option = None
let rules : (Vocab_of.season, Vocab_of.rank) Precedence.rules =
{ band; disposition; admit; vigil_feast }
-(* Normae n. 60 and Normae n. 5 together. THREE rules, in this priority order
- -- n. 60's own text defers to n. 5 explicitly ("servatis iis quae n. 5
- statuuntur"), so the two are one rule set, not two competing ones.
+(* Normae n. 56(f), n. 60 and n. 5 together. FOUR rules, in this priority
+ order -- n. 60's own text defers to n. 5 explicitly ("servatis iis quae
+ n. 5 statuuntur"), and n. 56(f) names the single collision it overrides
+ outright, so all three are one rule set, not three competing ones.
+
+ 0. ST JOSEPH ON PALM SUNDAY (Normae n. 56(f)): "Sollemnitas S. Ioseph,
+ ubi est de praecepto servanda, si cum Dominica in palmis de Passione
+ Domini occurrit, anticipatur sabbato praecedenti, die 18 martii" --
+ ANTICIPATED BACKWARD to the preceding Saturday, 18 March, the one
+ named exception to every other rule here searching or landing
+ forward. RESOLVED (of-normae-56f/W1, 2026-08-26; previously KNOWN
+ UNIMPLEMENTED -- see this value's own .mli for the full history,
+ including why it was blocked on a kernel contract change and what
+ that change cost). Checked first, ahead of rule 1, because it names
+ a more specific collision (one candidate, one date shape) than any
+ rule below.
1. THE ANNUNCIATION IN HOLY WEEK (Normae n. 60): "Sollemnitas vero
Annuntiationis Domini, quotiescumque occurrit aliquo die Hebdomadae
@@ -328,15 +341,19 @@ let rules : (Vocab_of.season, Vocab_of.rank) Precedence.rules =
whose occupant bands ABOVE 80 (entry 8 x 10).
TERMINATION, which Rite.t.transfer_target requires the rite to guarantee:
- the search is bounded at [max_search_days] steps AND at {!domain_max_date},
- and returns the last date visited -- never raises -- if either bound is
- reached before a free day is found. That bound is STRUCTURAL -- it does
- not appeal to the real calendar's own shape -- because Calendar's round
- guard bounds ROUNDS, not the internal search of a single call, and an
- unbounded walk would hang the caller outright before that guard was ever
- consulted; the domain bound additionally prevents the walk from ever
- calling [occupant] on a date {!Computus.gregorian_easter} cannot construct
- (see {!domain_max_date}'s own comment). *)
+ rules 1-3's search is bounded at [max_search_days] steps AND at
+ {!domain_max_date}, and returns the last date visited -- never raises --
+ if either bound is reached before a free day is found. That bound is
+ STRUCTURAL -- it does not appeal to the real calendar's own shape --
+ because Calendar's round guard bounds ROUNDS, not the internal search of
+ a single call, and an unbounded walk would hang the caller outright
+ before that guard was ever consulted; the domain bound additionally
+ prevents the walk from ever calling [occupant] on a date
+ {!Computus.gregorian_easter} cannot construct (see {!domain_max_date}'s
+ own comment). Rule 0 needs no bound of its own: it runs no search at
+ all, computing [Date.add_days origin (-1)] directly, so it terminates
+ trivially -- the one shape {!Colitur_kernel.Rite.t.transfer_target}'s own
+ comment names as always safe regardless of direction. *)
let max_search_days = 400
@@ -361,8 +378,26 @@ let max_search_days = 400
let domain_max_date =
match Date.make ~year:9999 ~month:12 ~day:31 with Ok d -> d | Error e -> failwith e
+(* NOTE, found while adding [st_joseph_slug] below, NOT fixed here --
+ out of scope for the W1 task this touches: [annunciation_slug] does not
+ match the real shipped entry's own slug (data/of/calendar-2002.sexp has
+ "annunciation-of-the-lord", not "of-annunciation"), so Rule 1 below
+ never actually fires against real sanctoral data -- only against the
+ synthetic candidate test_precedence_of.ml's own [annunciation] builds
+ with the same placeholder slug. Whether this is output-visible on real
+ data was not established either way here; Rule 3's general forward
+ search is a plausible fallback that could reach the identical Easter+8
+ target by coincidence in the one case tested
+ ([test_annunciation_in_easter_octave]'s own comment says as much for a
+ collision NOT in Holy Week), but that is not the same claim as "the two
+ rules always agree", and this was not checked for Holy Week itself. *)
let annunciation_slug = "of-annunciation"
+(* The real shipped slug (data/of/calendar-2002.sexp) -- St Joseph's own
+ Rule 0 below is matched against the actual sanctoral entry, not a
+ placeholder, precisely because the note above exists. *)
+let st_joseph_slug = "joseph-husband-of-the-blessed-virgin-mary"
+
(* Tabula entries 1-8, scaled: a day is FREE to receive a transfer when its
occupant bands strictly above entry 8. *)
let entry_8_band = 80
@@ -373,9 +408,27 @@ let transfer_target (c : Vocab_of.rank Precedence.candidate) (origin : Date.t)
let easter = Computus.gregorian_easter year in
let offset d = Date.to_rata d - Date.to_rata easter in
let is_holy_week d = offset d >= -7 && offset d <= -1 in
- (* Rule 1. *)
- if Slug.to_string c.cel.slug = annunciation_slug && is_holy_week origin then
- Date.add_days easter 8
+ let is_palm_sunday d = offset d = -7 in
+ (* Rule 0 (Normae n. 56(f)): "Sollemnitas S. Ioseph, ubi est de praecepto
+ servanda, si cum Dominica in palmis de Passione Domini occurrit,
+ anticipatur sabbato praecedenti, die 18 martii" -- St Joseph, impeded
+ by Palm Sunday, is ANTICIPATED BACKWARD to the preceding Saturday, not
+ carried forward like every other rule here. A fixed one-day
+ displacement, not a search: [origin] is Palm Sunday itself whenever
+ this fires (a Sunday, by construction), so "the preceding Saturday"
+ and "the day before [origin]" are the same date, and computing it this
+ way -- rather than via [offset]/[easter] -- makes that identity exact
+ rather than incidental. This is the rite.mli-documented EARLIER-target
+ shape (rite.mli's own [transfer_target] comment): a single named
+ collision, no internal search, hence trivially terminating on its own.
+ Checked ahead of Rules 1-3, most-specific first, the same order this
+ whole function already keeps. *)
+ if Slug.to_string c.cel.slug = st_joseph_slug && is_palm_sunday origin then
+ Date.add_days origin (-1)
+ else if
+ (* Rule 1. *)
+ Slug.to_string c.cel.slug = annunciation_slug && is_holy_week origin
+ then Date.add_days easter 8
else
let season_of d = (Temporal_of.temporal d).Temporal.season in
let privileged_sunday d =
@@ -384,7 +437,6 @@ let transfer_target (c : Vocab_of.rank Precedence.candidate) (origin : Date.t)
| Vocab_of.Advent | Vocab_of.Lent | Vocab_of.Easter -> true
| Vocab_of.Christmas | Vocab_of.Ordinary_time -> false
in
- let is_palm_sunday d = offset d = -7 in
let is_easter_sunday d = offset d = 0 in
(* Rule 2. *)
if