aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/precedence_ef_repro_test.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_repro_test.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_repro_test.go')
-rw-r--r--internal/calendar/precedence_ef_repro_test.go160
1 files changed, 160 insertions, 0 deletions
diff --git a/internal/calendar/precedence_ef_repro_test.go b/internal/calendar/precedence_ef_repro_test.go
new file mode 100644
index 0000000..11fd6ca
--- /dev/null
+++ b/internal/calendar/precedence_ef_repro_test.go
@@ -0,0 +1,160 @@
+package calendar_test
+
+// Reproduction tests for three named EF precedence defects (colitur's
+// differential/oracle audit against lectio's ~66-line EF precedence
+// approximation, internal/calendar/precedence_ef.go). Each test fails before
+// its fix and passes after -- see the report for the exact pre-fix failure
+// message. These exercise the REAL tridentine sanctoral data
+// (caldata.Tridentine()) through the public Compute entry point, since the
+// bugs are about how real fixed-date feasts interact with the temporal
+// calendar, not about precedenceEF's arithmetic in isolation (that is
+// covered separately, package-internal, in precedence_ef_test.go).
+
+import (
+ "testing"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/caldata"
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+)
+
+func efCompute(date string) calendar.LiturgicalDay {
+ sel := calendar.DefaultSelection()
+ sel.Form = "old"
+ d, _ := time.Parse("2006-01-02", date)
+ return calendar.Compute(d.UTC(), sel, []calendar.Layer{caldata.Tridentine()})
+}
+
+// TestJosephYieldsToSundayOfLent: RG 91 entry 6 (Sundays of Lent, I class)
+// vs entry 11 (I-class feasts of the universal Church not above) -- the
+// Sunday holds; St Joseph (19 March) transfers to the next free day (RG 95,
+// RG 96), landing on the 20th. Before the fix (defect 4: Sunday ranks), Lent
+// Sundays were wrongly II class, so Joseph (I class) won outright every time
+// this collision occurred.
+func TestJosephYieldsToSundayOfLent(t *testing.T) {
+ for _, year := range []string{"2006", "2017", "2023", "2028", "2034", "2045"} {
+ sunday := efCompute(year + "-03-19")
+ if sunday.Observed.Slug == "joseph-spouse-of-the-bl-virgin-mary" {
+ t.Errorf("%s-03-19 observed = %q want the Lent Sunday (RG 91 entry 6 beats entry 11)", year, sunday.Observed.Slug)
+ }
+ next := efCompute(year + "-03-20")
+ if next.Observed.Slug != "joseph-spouse-of-the-bl-virgin-mary" {
+ t.Errorf("%s-03-20 observed = %q want joseph-spouse-of-the-bl-virgin-mary (transferred one day, RG 96)", year, next.Observed.Slug)
+ }
+ }
+}
+
+// TestTransferSkipsBothIAndIIClass: RG 96 -- an impeded I-class feast
+// transfers to the next day that is NOT ITSELF I OR II CLASS (not merely "not
+// I class"). 2011: the Sacred Heart (Friday after the Corpus Christi octave)
+// falls on 1 July and impedes the Precious Blood (also 1 July, fixed). 2
+// July is the Visitation (II class, fixed); 3 July is an ordinary II-class
+// Sunday. Both must be skipped; the Precious Blood lands on 4 July, and the
+// Visitation is observed, undisplaced, on its own day.
+func TestTransferSkipsBothIAndIIClass(t *testing.T) {
+ if got := efCompute("2011-07-01").Observed.Slug; got != "ef-sacred-heart" {
+ t.Fatalf("2011-07-01 observed = %q want ef-sacred-heart", got)
+ }
+ if got := efCompute("2011-07-02").Observed.Slug; got != "visitation-of-the-blessed-virgin-mary" {
+ t.Errorf("2011-07-02 observed = %q want visitation-of-the-blessed-virgin-mary (RG 96: the Precious Blood must skip past it, not displace it)", got)
+ }
+ if got := efCompute("2011-07-03").Observed.Slug; got != "ef-time-after-pentecost-sunday-3" {
+ t.Errorf("2011-07-03 observed = %q want ef-time-after-pentecost-sunday-3 (an ordinary II-class Sunday also blocks a I-class transfer)", got)
+ }
+ if got := efCompute("2011-07-04").Observed.Slug; got != "precious-blood-of-our-lord-jesus-christ" {
+ t.Errorf("2011-07-04 observed = %q want precious-blood-of-our-lord-jesus-christ (RG 96: first day that is neither I nor II class)", got)
+ }
+}
+
+// TestMatthewBeatsSeptemberEmberWednesday: RG 91 -- St Matthew (21 September,
+// II class) and the September Ember Wednesday are both II class; the table
+// decides the tie. Entry 16 (II-class feasts of the universal Church) sits
+// ABOVE entry 18 (II-class ferias, including the Ember days) -- the feast
+// wins, the Ember feria is only commemorated.
+func TestMatthewBeatsSeptemberEmberWednesday(t *testing.T) {
+ // 21 September falls on the September Ember Wednesday whenever the third
+ // Sunday of September is the 18th -- 2016 and 2022 both qualify.
+ for _, date := range []string{"2016-09-21", "2022-09-21"} {
+ day := efCompute(date)
+ if day.Observed.Slug != "matthew" {
+ t.Errorf("%s observed = %q want matthew (RG 91 entry 16 beats entry 18)", date, day.Observed.Slug)
+ }
+ }
+}
+
+// TestImmaculateConceptionBeatsAdventSunday: found, not named among the
+// seven, while verifying defect 4 (Sunday ranks) against the real sanctoral
+// data. RG 91 entry 4 (Immaculate Conception, Assumption BVM) sits ABOVE
+// entry 6 (Sundays of Advent/Lent/Passiontide) -- unlike an ORDINARY
+// I-class feast (entry 11, e.g. St Joseph), the Immaculate Conception
+// (8 December) is not impeded by the Advent Sunday it falls on at all. Before
+// defect 4's own fix, Advent Sundays were wrongly II class, so this was
+// accidentally right (I class beats II class outright); once Sundays became
+// I class the tie mattered for the first time.
+func TestImmaculateConceptionBeatsAdventSunday(t *testing.T) {
+ for _, year := range []string{"2013", "2019", "2024"} {
+ day := efCompute(year + "-12-08")
+ if day.Observed.Slug != "immaculate-conception-of-the-blessed-virgin-mary" {
+ t.Errorf("%s-12-08 observed = %q want immaculate-conception-of-the-blessed-virgin-mary (RG 91 entry 4 beats entry 6)", year, day.Observed.Slug)
+ }
+ }
+}
+
+// TestVigilOfChristmasSurvivesAdventSunday: found, not named among the
+// seven, while verifying defect 4 against the real sanctoral data -- and
+// worse than a wrong winner. RG 91 entry 5 (Vigil & Octave day of the
+// Nativity) also sits above entry 6, so the Vigil of Christmas (24 December)
+// is not impeded by falling on Advent IV either. Without this, defect 4
+// alone would have made the Advent Sunday win a genuine tie, sending the
+// Vigil into transferIfImpededEF's forward walk -- which has no way to
+// re-place a transfer that crosses the Dec31/Jan1 boundary (celebrationDate
+// re-resolves a fixed date using the year of whatever day is being queried,
+// so a walk landing in the following January can never match the query that
+// produced it). The Vigil did not move to the wrong day; it vanished for the
+// whole year.
+func TestVigilOfChristmasSurvivesAdventSunday(t *testing.T) {
+ for _, year := range []string{"2006", "2017", "2023", "2028"} {
+ day := efCompute(year + "-12-24")
+ if day.Observed.Slug != "vigil-of-christmas" {
+ t.Errorf("%s-12-24 observed = %q want vigil-of-christmas (RG 91 entry 5 beats entry 6; must not vanish)", year, day.Observed.Slug)
+ }
+ }
+}
+
+// TestPurificationBeatsFebruarySunday: the fixture behind classOf's own
+// Purification decision (scripts/gen-sanctoral-ef.go), committed here rather
+// than left as an assertion only, because the deciding years (2 February on
+// a Sunday: 2014, 2020, 2025, 2031, 2042, 2048) fall entirely outside this
+// repo's own missalemeum oracle snapshot (sources/snapshot.tar.gz,
+// 2026-01-01 .. 2027-12-31), so nothing else in this repository can
+// reproduce the evidence classOf's decision rests on. (2036, sometimes
+// quoted alongside 2025/2031 as a third example: 2 February 2036 is in fact
+// a SATURDAY, `date -d 2036-02-02 +%A` -- checked here rather than repeated;
+// 2042 is the correct next instance after 2031.)
+//
+// RG 91 entry 14 ("Festa Domini II classis") sits above entry 15
+// ("Dominicae II classis"), which sits above entry 16 ("Festa II classis
+// Ecclesiae universae, quae non [sunt Domini]") -- entry 14 takes an
+// occurring Sunday's place outright, entry 16 is merely commemorated on
+// one. The Purification (2 February) matches entry 14's pattern, not entry
+// 16's, independently fetched live from missalemeum for five different
+// Sundays: 2014-02-02, 2020-02-02, 2025-02-02, 2031-02-02, 2042-02-02 (all
+// "id":"sancti:02-02:2:w", "commemorations":[]). Control:
+// nativity-of-the-blessed-virgin-mary (8 September, an undisputed ordinary
+// Marian feast, entry 16's own pattern) on a Sunday -- 2019-09-08 -- shows
+// the SUNDAY observed, the feast commemorated instead: the opposite shape,
+// proving the Purification's own treatment is a deliberate pattern in the
+// oracle, not a gap. This is a decision AGAINST the calendarium's own title
+// ("IN PURIFICATIONE B. MARIAE VIRG.") and RG 120(b) (which files 2
+// February under the white-colour rule's "B. Mariae Virg." heading, kept
+// separate from 120(a)'s "Domini" heading) -- both point BVM; the
+// occurrence-behaviour evidence here points Domini. See classOf's own doc
+// comment for the fuller account; not re-argued here.
+func TestPurificationBeatsFebruarySunday(t *testing.T) {
+ for _, year := range []string{"2014", "2020", "2025", "2031", "2042"} {
+ day := efCompute(year + "-02-02")
+ if day.Observed.Slug != "purification-of-the-blessed-virgin-mary" {
+ t.Errorf("%s-02-02 observed = %q want purification-of-the-blessed-virgin-mary (RG 91 entry 14 beats entry 15)", year, day.Observed.Slug)
+ }
+ }
+}