diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-22 16:40:07 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-22 16:40:24 +0200 |
| commit | 60e87914718dd2c8e69bb89d8cc48d6bef9bfc74 (patch) | |
| tree | 814f451ac97e61d8ab83a2e24877b1b5223734db /test/test_golden.ml | |
| parent | 219c0ca6884a634e83293c62b27cca990fa25854 (diff) | |
| download | colitur-60e87914718dd2c8e69bb89d8cc48d6bef9bfc74.tar.gz colitur-60e87914718dd2c8e69bb89d8cc48d6bef9bfc74.zip | |
feat(ef): implement the calendarium's bissextile February shift
The Missale Romanum's calendarium footnote for February (LT.txt:5011-5014,
scan-corroborated scan2.txt:3050-3058): in a leap year the sixth kalends of
March (24 February) is doubled rather than a 29th day appended, so St
Matthias moves 24->25 February and St Gabriel of Our Lady of Sorrows
27->28, with 24 February itself carrying no fixed office that year.
data/ef/expected-divergences-lms.sexp entry L3 tracked this as an open gap.
Implemented as the rubric's own general mechanism (every fixed entry from
24 through 28 February shifts one civil day later in a leap year), not as
"move these two named saints": the two readings are indistinguishable on
shipped data (nothing else is fixed in that window) and diverge only for a
future --overlay entry in the same range, which the mechanism reading
covers correctly and a two-saints special case would not. This project has
already been bitten once (RG 16(a)) by a rule implemented against shipped
data's coincidental shape rather than the rubric itself.
Kernel stays rite-agnostic: Rite.t gains a fixed_key field (Date.t -> (int
* int) option), the same seam easter already establishes, read only by
Layer.on_date's FIXED half -- the movable half (Easter_offset/Nth_weekday)
is untouched. Default is the identity mapping, an optional argument on
on_date, so every existing caller and every rite that supplies nothing is
byte-identical to before this field existed. Date.is_leap is exposed in
date.mli (already existed in date.ml) so the rite reads the kernel's own
single-sourced leap-year definition. The EF implementation lives in
Rite_ef.Temporal_ef.bissextile_fixed_key, fully cited, wired into
Rite_ef.context.
Blast radius, measured over the full 1583-9999 domain (Calendar.year run
twice per civil year, real fixed_key vs identity, every day diffed): all
2,041 leap years in the domain show a visible change for each saint;
6,983 individual liturgical days change total, zero unclassified, across
four shapes (Matthias vacates 24 Feb in 1,803 years / occupies 25 Feb in
1,811; Gabriel vacates 27 Feb in 1,699 / occupies 28 Feb in 1,670).
L3's own prior estimate (1,650 years, observed-outright only) is confirmed
close on the same definition (1,677 measured); the broader observed-or-
commemorated count is higher, not lower, showing the narrow estimate
undercounted visible impact rather than overcounting it.
The lectio differential (2005-2050 fixture) gains a new cited entry, C40
in data/ef/expected-divergences.sexp: lectio implements no such shift, so
every leap year in its window now diverges on the two shifted days (21
rows, not the naive 44 -- the comparator never compares commemorations, so
a side where the shifted saint has zero comparator-visible footprint on
both engines produces no row). The LMS Ordo's 2023-2024 fixture -- the
window L3 was originally found in -- now matches on the Creed comparison;
its formulary-override bucket count is corrected 182->181 (24 February
leaves the counted Proper population, becoming Own_slug-sourced). The
missalemeum oracle fixtures (2026-2027, 2038, 2035) cover no leap year and
are unaffected.
L3 is closed: converted from an active allow-list record to a prose
closure paragraph (L1's own precedent), citation preserved verbatim, fix
and measured blast radius recorded. The id-list assertion narrows to [L4]
alone, and the now-dead "2024-02-24" -> "L3" date mapping is removed.
Six golden values pinned across four new test cases: Matthias in a leap
year (both the vacated and occupied sides) and a common-year control;
Gabriel likewise, deliberately choosing a leap year where he is admitted
only as an ordinary commemoration rather than winning outright, a
different shape from Matthias's.
Mutation-tested: forcing fixed_key back to identity reddens exactly five
test cases -- the differential's Layer C count-pin (C40 drops to 0 actual
vs 21 expected), the LMS Ordo's Creed and formulary-bucket checks, and
both new golden pins -- confirming the tests actually exercise the fix.
Reverted; dune test, the exhaustive sweep, and make check are all green.
Diffstat (limited to 'test/test_golden.ml')
| -rw-r--r-- | test/test_golden.ml | 71 |
1 files changed, 70 insertions, 1 deletions
diff --git a/test/test_golden.ml b/test/test_golden.ml index ba25d8a..d680720 100644 --- a/test/test_golden.ml +++ b/test/test_golden.ml @@ -1315,6 +1315,67 @@ let test_rogation_wednesday_yields_to_a_feast_2026 () = "2026-05-13 wednesday season=paschaltide week=6 slug=ef-ascension-vigil rank=class-2 colour=white \ subject=temporal name_la=- comms=[robert-bellarmine:ordinary] in=- out=[]" +(* ---- The bissextile (leap-year) calendarium footnote, bissextile-shift + task, 2026-08-22 ---- + + The Missale Romanum's own CALENDARIUM table, February, footnote + (docs/research/LT.txt:5011-5014, scan-corroborated docs/research/ + scan2.txt:3050-3058): "In anno bissextili mensis februarius est dierum + 29, et festum S. Matthiae celebratur die 25 februarii, ac festum S. + Gabrielis a Virgine perdolente 28 februarii, et bis dicitur sexto + calendas, id est die 24 et die 25" -- in a leap year the sixth kalends + of March (24 February) is doubled rather than a 29th day appended, so + St Matthias (ordinarily 24 February) is kept on 25 February and St + Gabriel of Our Lady of Sorrows (ordinarily 27 February) on 28 February; + 24 February itself carries no fixed office that year. + {!Rite_ef.Temporal_ef.bissextile_fixed_key} implements this as the + rubric's own general MECHANISM, so a leap year moves the VACATED date's + slug/rank/colour away and the OCCUPIED date's toward it -- both sides + pinned for each saint, plus one common-year control apiece proving the + identity default is unchanged where the footnote does not apply. *) + +(* Matthias, 2044 (leap): 24 February carries no fixed office at all + (Class4 temporal only, zero commemorations); Matthias (Class2, an + Apostle) wins 25 February outright against that day's own Class4 + feria. *) +let test_bissextile_matthias_leap_2044 () = + check ~msg:"leap year: 24 February carries no fixed office, Matthias has moved away" 2044 2 24 + "2044-02-24 wednesday season=septuagesima week=2 slug=ef-septuagesima-2-wednesday rank=class-4 colour=violet \ + subject=temporal name_la=- comms=[] in=- out=[]"; + check ~msg:"leap year: Matthias observed outright on 25 February, not 24" 2044 2 25 + "2044-02-25 thursday season=septuagesima week=2 slug=matthias rank=class-2 colour=red subject=saint name_la=- \ + comms=[] in=- out=[]" + +(* Matthias, 2025 (common): unaffected control -- the identity default + applies, Matthias stays at his ordinary 24 February. *) +let test_bissextile_matthias_common_2025 () = + check ~msg:"common year: Matthias unaffected, still 24 February" 2025 2 24 + "2025-02-24 monday season=septuagesima week=2 slug=matthias rank=class-2 colour=red subject=saint name_la=- \ + comms=[] in=- out=[]" + +(* Gabriel of Our Lady of Sorrows, 2008 (leap): 27 February carries no + trace of him at all (not even a commemoration -- fixed_key removes him + from the candidate set entirely, RG 111's admission machinery never + sees him); on 28 February he is admitted as an ORDINARY commemoration + (Class3 ties the day's own privileged Lenten feria, which keeps + observed). A different shape from Matthias's outright win above, + deliberately pinned: the mechanism moves WHERE the saint is tried, not + whether he wins once he gets there. *) +let test_bissextile_gabriel_leap_2008 () = + check ~msg:"leap year: 27 February carries no trace of Gabriel, he has moved away" 2008 2 27 + "2008-02-27 wednesday season=lent week=3 slug=ef-lent-3-wednesday rank=class-3 colour=violet subject=temporal \ + name_la=- comms=[] in=- out=[]"; + check ~msg:"leap year: Gabriel admitted as an ordinary commemoration on 28 February, not 27" 2008 2 28 + "2008-02-28 thursday season=lent week=3 slug=ef-lent-3-thursday rank=class-3 colour=violet subject=temporal \ + name_la=- comms=[gabriel-of-our-lady-of-sorrows:ordinary] in=- out=[]" + +(* Gabriel of Our Lady of Sorrows, 2025 (common): unaffected control -- + observed outright at his ordinary 27 February. *) +let test_bissextile_gabriel_common_2025 () = + check ~msg:"common year: Gabriel unaffected, still observed on 27 February" 2025 2 27 + "2025-02-27 thursday season=septuagesima week=2 slug=gabriel-of-our-lady-of-sorrows rank=class-3 colour=white \ + subject=saint name_la=- comms=[] in=- out=[]" + (* RG 128, transcribed in docs/research/rules-register.md ยง3b: violet is used for "II/III-class vigils outside Paschaltide". Two of colitur's five vigils disagreed with that rule until the ef-oconnell-rubrics branch -- the @@ -1510,5 +1571,13 @@ let suite = Alcotest.test_case "RG87: Rogation Wednesday admitted (2024)" `Quick test_rogation_wednesday_admitted_2024; Alcotest.test_case "RG87/113: Rogation Wednesday yields to an impeded feast (2026)" `Quick - test_rogation_wednesday_yields_to_a_feast_2026 + test_rogation_wednesday_yields_to_a_feast_2026; + Alcotest.test_case "bissextile footnote: Matthias moves 24->25 February (leap, 2044)" `Quick + test_bissextile_matthias_leap_2044; + Alcotest.test_case "bissextile footnote: Matthias unaffected (common, 2025)" `Quick + test_bissextile_matthias_common_2025; + Alcotest.test_case "bissextile footnote: Gabriel of Our Lady of Sorrows moves 27->28 February (leap, 2008)" + `Quick test_bissextile_gabriel_leap_2008; + Alcotest.test_case "bissextile footnote: Gabriel of Our Lady of Sorrows unaffected (common, 2025)" `Quick + test_bissextile_gabriel_common_2025 ] ) |
