aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/precedence_ef.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-14 13:22:16 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-14 13:22:16 +0200
commit913974b10a993af251b25125d1a417c452ad785c (patch)
tree5dd82811c100de6daa995bebd115a12434296508 /internal/calendar/precedence_ef.go
parentd7da4b09f7775276231d0241cfe2700d247728ee (diff)
parent3b32c002d3eddda5ece9422442717657b9fee63b (diff)
downloadlectio-913974b10a993af251b25125d1a417c452ad785c.tar.gz
lectio-913974b10a993af251b25125d1a417c452ad785c.zip
Merge branch 'polish-ui-and-calendar': the gomobile facade and the EF calendar fixes
Two bodies of work that shared a branch. The gomobile facade (2026-08-03..05): mobile.PartLabels, Days, and the observed rank on DayInfo, so dlectio stops hardcoding part IDs and rank strings; the 1962 part labels become i18n data; the documented gomobile bind command is corrected so it reproduces the shipped .aar. The EF calendar fixes (2026-08-12): seven defects found by differencing this engine against colitur, a second 1962 implementation built from the Missal's General Rubrics rather than from this codebase. RG 96 transfers were not skipping II-class days; a II-class privileged feria was not yielding to a feast; Sunday ranks, the two Rose Sundays and Holy Thursday's colour were wrong; and scripts/gen-sanctoral-ef inferred ranks, deduped and tagged classes wrongly, which put 15 III-class feasts into the shipped tridentine-calendar.ini as bare commemorations and dropped four entries outright. Holy Thursday was violet in both engines, which is how a shared lineage hides a defect: this project's ini is generated from missalemeum and colitur's data was bootstrapped from here, so an error inherited by both is invisible to a differential. It took the Missal itself to see it. The EF oracle test now asserts rank and colour, not season alone. One known gap is recorded in the source rather than fixed, as out of scope: RG 95 chained transfers (calendar.go).
Diffstat (limited to 'internal/calendar/precedence_ef.go')
-rw-r--r--internal/calendar/precedence_ef.go86
1 files changed, 72 insertions, 14 deletions
diff --git a/internal/calendar/precedence_ef.go b/internal/calendar/precedence_ef.go
index 4fda626..9f5016f 100644
--- a/internal/calendar/precedence_ef.go
+++ b/internal/calendar/precedence_ef.go
@@ -1,6 +1,9 @@
package calendar
-import "sort"
+import (
+ "sort"
+ "strings"
+)
// efRankOrder orders EF (1960) ranks: class-1 highest, then class-2..4,
// commemoration, ferial lowest.
@@ -22,34 +25,89 @@ func efRankOrder(r Rank) int {
}
// precedenceEF ranks an EF candidate for occurrence (lower = higher precedence):
-// by class first, then a tie-break at equal class (1960 occurrence table).
+// by class first, then a tie-break at equal class (RG 91's Table of
+// Precedence, read entry-by-entry within each class).
//
-// The tie-break is asymmetric by season. An ORDINARY feria (per annum, Advent,
-// Septuagesima) yields to an equal-class feast — the feast is celebrated and the
-// feria commemorated (e.g. St Francis Xavier, Dec 3, on an Advent feria). The
-// ferias of Lent and Passiontide are privileged: they outrank an equal-class
-// feast, which is only commemorated. Sundays and named temporal feasts likewise
-// win their ties. The *2 class spacing means the ±1 tie-break never crosses a
-// class boundary.
+// The tie-break is asymmetric by season AND, at class 2, by whether the
+// temporal day is a Sunday or a privileged FERIA:
+// - III/IV class: an ORDINARY feria (per annum, Advent to 16 Dec,
+// Septuagesima) yields to an equal-class feast (entries 24/25 above the
+// feria) -- the feast is celebrated, the feria commemorated (e.g. St
+// Francis Xavier, Dec 3). The ferias of Lent and Passiontide are
+// privileged (entry 22, ABOVE entry 24): they outrank an equal-class
+// feast, which is only commemorated.
+// - II class: an ordinary SUNDAY wins its tie (entry 15, above entry 16's
+// feasts) -- but a II-class privileged FERIA (the Ember days, the
+// late-Advent 17-23 Dec ferias, entry 18) sits BELOW entry 16 and yields
+// to an equal-class feast, the opposite of a Sunday's own tie. This is
+// the same table shape as III class's Advent/per-annum ferias, just
+// inverted for which II-class temporal days count as "privileged".
+// - I class: Sundays (entry 6) and I-class ferias (entry 7, Ash
+// Wednesday/Holy Week) both sit above entry 11's feasts, so they always
+// win -- there is no I-class "ordinary feria yields" case at all.
+//
+// The *2 class spacing means the ±1 tie-break never crosses a class boundary.
func precedenceEF(c candidate) int {
p := (6 - efRankOrder(c.Cel.Rank)) * 2 // class-1 -> 2, ferial -> 12
if c.Temporal {
- ordinaryFeria := (c.Cel.Rank == RankClass3 || c.Cel.Rank == RankClass4) &&
- c.Season != Lent && c.Season != Passiontide
- if ordinaryFeria {
- p++ // an ordinary feria yields to an equal-class feast
+ yieldsToFeast := false
+ switch c.Cel.Rank {
+ case RankClass3, RankClass4:
+ yieldsToFeast = c.Season != Lent && c.Season != Passiontide
+ case RankClass2:
+ // A II-class Sunday (entry 15) wins; a II-class privileged feria --
+ // Ember days, the late-Advent 17-23 Dec ferias (entry 18) -- yields
+ // to an equal-class feast (entry 16), e.g. St Matthew (21 Sep)
+ // beating the September Ember Wednesday, or St Thomas (21 Dec)
+ // beating an Advent late feria.
+ yieldsToFeast = !c.Sunday
+ }
+ if yieldsToFeast {
+ p++
} else {
- p-- // Sundays, named feasts, and penitential ferias win their ties
+ p-- // Sundays, I-class ferias, and penitential (Lent/Passiontide) ferias win their ties
}
} else if c.Cel.Class == ClassLord && c.Cel.Rank == RankClass2 {
// A II class feast of the Lord takes the place of a II class Sunday it
// falls on (unlike a saint's feast, which is only commemorated). Give it
// the edge over the Sunday's tie-break bonus.
p -= 2
+ } else if c.Cel.Rank == RankClass1 && beatsClass1Sunday(c.Cel.Slug) {
+ // RG 91 entries 4 (Immaculate Conception, Assumption BVM) and 5
+ // (Vigil & Octave day of the Nativity) both sit ABOVE entry 6
+ // (Sundays of Advent/Lent/Passiontide, Low Sunday) -- unlike an
+ // ORDINARY I-class feast (entry 11, e.g. St Joseph, the Precious
+ // Blood), which yields to a I-class Sunday, these win the tie.
+ // Found while verifying defect 4 (Sunday ranks): before that fix,
+ // Advent/Lent Sundays were wrongly II class, so these feasts beat
+ // them outright on base class alone, accidentally right; once
+ // Sundays became I class the tie-break mattered, and without this
+ // branch the Sunday would wrongly win. Two live cases in the fixed
+ // calendar: the Immaculate Conception (8 December) on an Advent
+ // Sunday, and the Vigil of Christmas (24 December) on Advent IV --
+ // the latter is not merely a wrong winner but a WORSE bug without
+ // this branch: transferIfImpededEF has no way to re-place a
+ // transfer that crosses the Dec31/Jan1 boundary (celebrationDate
+ // re-resolves a fixed date using the YEAR OF THE DAY BEING QUERIED,
+ // so a walk landing in January of the following year can never
+ // match the query that produced it), so the Vigil simply vanished
+ // for the year instead of landing on the wrong day. The Assumption
+ // (15 August) never falls in Advent or Lent, so its own share of
+ // this branch is citation-complete but not live.
+ p -= 2
}
return p
}
+// beatsClass1Sunday reports whether slug is one of RG 91's entries 4 or 5 --
+// the Immaculate Conception, the Assumption, or the Vigil of Christmas --
+// all of which sit above entry 6's Sundays in the Table of Precedence.
+func beatsClass1Sunday(slug string) bool {
+ return strings.Contains(slug, "immaculate-conception") ||
+ strings.Contains(slug, "assumption-of-the-blessed-virgin-mary") ||
+ slug == "vigil-of-christmas"
+}
+
// pickEF returns the observed EF celebration and the commemorations, ordered
// deterministically by precedence then slug.
func pickEF(cands []candidate) (candidate, []candidate) {