aboutsummaryrefslogtreecommitdiff
path: root/lib/rites
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rites')
-rw-r--r--lib/rites/rite_ef/precedence_ef.ml88
-rw-r--r--lib/rites/rite_ef/precedence_ef.mli32
2 files changed, 80 insertions, 40 deletions
diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml
index b9b1731..0feb3e0 100644
--- a/lib/rites/rite_ef/precedence_ef.ml
+++ b/lib/rites/rite_ef/precedence_ef.ml
@@ -405,9 +405,16 @@ let disposition ~(winner : Vocab_ef.rank Precedence.candidate)
branch that completes Task 7's All Souls fix (register line 334, RG
91 entry 8): All Souls is I class, not a vigil, and not a Sunday
slug, so once it loses to an occurring Sunday it still 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. *)
+ and transfers -- to 3 November, now DIRECTLY authorised by RG 96
+ Attamen (b) (primary-source-verified 2026-08-12): "Commemoratio
+ omnium Fidelium defunctorum, quando occurrit cum dominica,
+ transfertur, tamquam in sedem propriam, in feriam II sequentem" --
+ when it coincides with a Sunday, transferred, as to its own proper
+ seat, to the following Monday. Previously this rested only on entry
+ 8's own parenthetical plus the general RG 96 walk, which happened to
+ produce the right date; WHERE it lands either way is
+ Rite.transfer_target's job, not this function's -- disposition only
+ says THAT it moves. *)
Precedence.Transfer
else
(* RG 95's other branch: "aut commemorantur aut penitus omittuntur" --
@@ -545,14 +552,23 @@ let admit ~(observed : Vocab_ef.rank Precedence.candidate)
already draws for RG 111 ({!dignity}, not [band]). *)
let is_blocking (rank : Vocab_ef.rank) = rank = Vocab_ef.Class1 || rank = Vocab_ef.Class2
-(* RG 96's own named exception, register-transcribed: "(Annunciation
- exception): -> Monday after Low Sunday." Identified by slug -- the same
- convention this file already uses to pick out one specific celebration
- from a rank/status shape shared by many others ({!nativity_octave_prefix},
- [is_ember_18]'s date anchors) -- not an RG citation itself: RG 96 does not
- encode how a computer recognises "the Annunciation", only what happens to
- it once recognised. data/ef/sanctoral.sexp's own bootstrapped slug (Task
- 10), reused verbatim rather than guessed. *)
+(* RG 96's own named exception (docs/research/rules-register.md §4,
+ "Transfer/translation", RG 96 Attamen (a) -- primary-source-verified
+ 2026-08-12, corrected from an earlier unconditional transcription; see
+ the register's own correction note). Verbatim: "festum Annuntiationis
+ B. Mariae Virg., quando est transferendum post Pascha, transfertur,
+ tamquam in sedem propriam, in feriam II post dominicam in albis" -- when
+ [the feast] is to be transferred PAST EASTER, [it] is transferred, as to
+ its own proper seat, to the Monday after Low Sunday. The exception is
+ CONDITIONAL on that "past Easter" clause -- {!transfer_target} tests it
+ by comparing the GENERAL RG 96 target against Easter itself, not by
+ testing the date here. Identified by slug -- the same convention this
+ file already uses to pick out one specific celebration from a rank/
+ status shape shared by many others ({!nativity_octave_prefix},
+ [is_ember_18]'s date anchors) -- not an RG citation itself: RG 96 does
+ not encode how a computer recognises "the Annunciation", only what
+ happens to it once recognised. data/ef/sanctoral.sexp's own bootstrapped
+ slug (Task 10), reused verbatim rather than guessed. *)
let annunciation_slug = "annunciation-of-the-blessed-virgin-mary"
(* Not an RG citation -- a defensive engineering ceiling, the same role
@@ -620,27 +636,39 @@ let rec search_from (occupant : Date.t -> Vocab_ef.rank Celebration.t) (steps :
(* [transfer_target]'s contract (rite.mli): total, terminating, and its
result is always strictly after [origin]. Terminating: [search_from]'s
- own structural bound, above. Strictly after [origin]: the ordinary branch
- starts the search at [Date.add_days origin 1] and [search_from] only ever
- advances forward from its own starting point, so the result is always >=
- origin + 1. The Annunciation branch starts instead at the Monday after
- Low Sunday for [origin]'s own civil year -- NOT provably later than
- [origin] by the code alone, but true of every representable year: the
- Annunciation's [origin] is always 25 March (Date_spec.Fixed in
+ own structural bound, above. Strictly after [origin]: the general branch
+ is exactly [search_from]'s own result starting at [Date.add_days origin
+ 1], which only ever advances forward from there, so it is always >=
+ origin + 1. The Annunciation branch, when it fires, instead searches from
+ the Monday after Low Sunday for [origin]'s own civil year -- NOT provably
+ later than [origin] by the code alone, but true of every representable
+ year: the Annunciation's [origin] is always 25 March (Date_spec.Fixed in
data/ef/sanctoral.sexp), Easter always falls within that SAME civil year
in [22 March, 25 April] (Computus's own documented range, register §0),
so Low Sunday (Easter + 7) falls in [29 March, 2 May] and the Monday
- after it in [30 March, 3 May] -- always after 25 March. *)
+ after it in [30 March, 3 May] -- always after 25 March.
+
+ RG 96 Attamen (a) (see {!annunciation_slug}'s own comment) makes the
+ Annunciation exception CONDITIONAL on the general walk carrying the
+ feast past Easter -- so the general target is always computed FIRST,
+ for every candidate, and only overridden for the Annunciation when that
+ target itself falls after Easter Sunday. A version of this function that
+ tested the DATE of [origin] instead (e.g. "is 25 March within some fixed
+ window of Easter") would be re-deriving the register's own "quando est
+ transferendum post Pascha" condition from first principles, exactly the
+ kind of guess this project's "a wrong citation is worse than a missing
+ one" rule warns against; comparing the general target against Easter
+ directly tests the rubric's own words. *)
let transfer_target (c : Vocab_ef.rank Precedence.candidate) (origin : Date.t)
(occupant : Date.t -> Vocab_ef.rank Celebration.t) : Date.t =
- let start =
- if Slug.to_string c.Precedence.cel.Celebration.slug = annunciation_slug then
- (* Low Sunday = Easter + 7 (register §0, temporal_ef.ml's [off 7]); the
- Monday after it = Easter + 8. Searched onward from there exactly
- like the general case searches from [origin + 1] -- "only if that
- day is itself blocked" (rite.mli) is [search_from]'s ordinary
- behaviour, not a second mechanism. *)
- Date.add_days (Computus.gregorian_easter (Date.year origin)) 8
- else Date.add_days origin 1
- in
- search_from occupant 0 start
+ let general_target = search_from occupant 0 (Date.add_days origin 1) in
+ let is_annunciation = Slug.to_string c.Precedence.cel.Celebration.slug = annunciation_slug in
+ let easter = Computus.gregorian_easter (Date.year origin) in
+ if is_annunciation && Date.compare general_target easter > 0 then
+ (* Low Sunday = Easter + 7 (register §0, temporal_ef.ml's [off 7]); the
+ Monday after it = Easter + 8. Searched onward from there exactly
+ like the general case searches from [origin + 1] -- "only if that
+ day is itself blocked" (rite.mli) is [search_from]'s ordinary
+ behaviour, not a second mechanism. *)
+ search_from occupant 0 (Date.add_days easter 8)
+ else general_target
diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli
index 7318ccd..8234bf3 100644
--- a/lib/rites/rite_ef/precedence_ef.mli
+++ b/lib/rites/rite_ef/precedence_ef.mli
@@ -187,11 +187,23 @@ val annunciation_slug : string
RG 96's own rule: the next following day whose currently-resolved
occupant is not I or II class (read off [Vocab_ef.rank], RG 8's dignity
-- not {!band}'s finer occurrence-table entry, the same distinction
- {!admit} draws for RG 111). RG 96's own named exception: the
- Annunciation ({!annunciation_slug}) does not search from [origin + 1] at
- all -- it starts at the Monday after Low Sunday for [origin]'s own civil
- year, searching onward from there only if that day is itself occupied by
- a blocking class.
+ {!admit} draws for RG 111). This general target is computed for EVERY
+ candidate, always, first.
+
+ RG 96's own named exception (Attamen (a), primary-source-verified --
+ see {!annunciation_slug}'s comment for the Latin and the register's own
+ correction note): for the Annunciation specifically, IF that general
+ target would fall after Easter Sunday itself ("quando est transferendum
+ post Pascha" -- when it is to be transferred past Easter), the
+ Annunciation is placed instead at the Monday after Low Sunday (its
+ [sedes propria]), searching onward from there only if that day is
+ itself occupied by a blocking class. The exception is CONDITIONAL, not
+ unconditional: an Annunciation impeded for a reason that resolves
+ BEFORE Easter (e.g. an ordinary Lent Sunday with a free feria the next
+ day) takes the general target like any other I-class feast. Operationally
+ the condition holds exactly when 25 March falls close enough to Easter
+ that the general walk crosses it -- concretely, when 25 March itself
+ falls within Holy Week or Easter Week.
Total, terminating, and its result is always strictly later than
[origin] -- {!Colitur_kernel.Rite.t}.transfer_target's own obligations,
@@ -205,10 +217,10 @@ val annunciation_slug : string
[domain_max_date]), not by an argument about the real 1962 calendar's
own structure, so a rite/data shape this function has not anticipated
fails FINITELY rather than hanging or crashing the caller. Strictly
- later than [origin]: the ordinary search
- starts at [origin + 1] and only ever advances forward from there; the
- Annunciation's own starting point is provably later than 25 March for
- every representable year (Easter's documented range, register §0) --
- see the .ml for the full argument. *)
+ later than [origin]: the general search starts at [origin + 1] and only
+ ever advances forward from there; the Annunciation's own alternate
+ starting point is provably later than 25 March for every representable
+ year (Easter's documented range, register §0) -- see the .ml for the
+ full argument. *)
val transfer_target :
Vocab_ef.rank Precedence.candidate -> Date.t -> (Date.t -> Vocab_ef.rank Celebration.t) -> Date.t