From 2247e8c27fbb3a407a9c2b3923850c7df93d77b4 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Tue, 11 Aug 2026 14:20:13 -0400 Subject: [PATCH] calendar: exempt Vigils from Our Lady's Saturday's demotion Refined per direct correction: Our Lady's Saturday is really only a Simplex-strength standing, not a privileged-feria-minor-shaped one -- a Vigil keeps forcing its own primacy on a Saturday the same way it already does any other day (see calendar/commemorations.ts's own transferDirectionOf special-casing for Vigils elsewhere). Only a Simplex saint alone now loses to it; a Vigil, along with anything Semiduplex-or-higher, wins outright and Marian Saturday doesn't apply. Un-does the two test-date moves this same regression prompted last time (Vigil of St. Lawrence, Vigil of St. John Baptist) -- both dates' original years work again now that the fix is right. Co-Authored-By: Claude Sonnet 5 --- src/calendar/index.ts | 21 +++++++++++-------- tests/calendar/august-sanctoral.test.ts | 5 +---- tests/calendar/june-sanctoral.test.ts | 7 ++----- tests/calendar/marian-saturday.test.ts | 27 ++++++++++++++++--------- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/calendar/index.ts b/src/calendar/index.ts index 11a81b0..782b722 100644 --- a/src/calendar/index.ts +++ b/src/calendar/index.ts @@ -140,13 +140,18 @@ function applyChristTheKing(isoDate: string, winner: DayWinner, commemorations: /** * Layered on top of everything above, same spirit as applyOctaves: never * changes decideOccurrence's own rules, just relabels the result on a free - * Saturday. Real standing of its own (per direct instruction, mirrors - * privileged-feria-minor exactly) — a Semiduplex-or-higher saint still - * wins outright and this never applies; a Simplex (or Vigil) saint loses - * to it and is commemorated instead, same as an Advent feria would demote - * one. `ordinary-feria` is the only category this touches — every - * privileged season/day (Ember Saturdays, Advent, Lent, ...) already has - * its own real standing and keeps it untouched. + * Saturday. Real standing of its own, but only really a Simplex-strength + * one (per direct instruction, refined from an earlier "mirrors + * privileged-feria-minor exactly" pass that also swept in Vigils, which + * turned out wrong: a Vigil keeps forcing its own primacy on a Saturday + * the same way it already does any other day, same as Simplex always has + * — see calendar/commemorations.ts's own transferDirectionOf for Vigil's + * usual special standing elsewhere) — a Simplex saint alone loses to it + * and is commemorated instead; anything Semiduplex-or-higher, or a Vigil, + * still wins outright and this never applies. `ordinary-feria` is the + * only category this touches — every privileged season/day (Ember + * Saturdays, Advent, Lent, ...) already has its own real standing and + * keeps it untouched. * * The relabel (not just leaving `winner` as the plain temporal id) is * what lets getDayLabel/getBenedictusAntiphon/getDayCollects/getLaudsPsalmodyOverride @@ -175,7 +180,7 @@ function applyMarianSaturday( if (winner.kind === 'temporal') { return MARIAN_SATURDAY; } - if (isAtLeast(winner.rank, 'semiduplex')) { + if (winner.rank !== 'simplex') { return winner; } commemorations.push({ kind: 'sanctoral', id: winner.id, name: winner.name, rank: winner.rank }); diff --git a/tests/calendar/august-sanctoral.test.ts b/tests/calendar/august-sanctoral.test.ts index 0988228..2007237 100644 --- a/tests/calendar/august-sanctoral.test.ts +++ b/tests/calendar/august-sanctoral.test.ts @@ -53,10 +53,7 @@ describe('August sanctoral pull (first pass)', () => { }); it("the Vigil of St. Lawrence's own vigil rank forces primacy over a real, otherwise-unranked secondary saint", () => { - // 2025-08-09 is a Saturday -- collides with the generic Saturday-of- - // Mary votive this test isn't about (see tests/calendar/marian- - // saturday.test.ts); 2024 keeps it a plain midweek day. - const day = resolveDay('2024-08-09'); + const day = resolveDay('2025-08-09'); expect(day.winner).toEqual({ kind: 'sanctoral', id: 'vigil-of-st-lawrence', diff --git a/tests/calendar/june-sanctoral.test.ts b/tests/calendar/june-sanctoral.test.ts index 5b8138a..18d4abd 100644 --- a/tests/calendar/june-sanctoral.test.ts +++ b/tests/calendar/june-sanctoral.test.ts @@ -40,11 +40,8 @@ describe('June sanctoral pull (first pass)', () => { }); it('the Nativity of St. John the Baptist wins outright at Duplex I. classis, distinct from his Vigil the day before', () => { - // 2035-06-23 is a Saturday -- collides with the generic Saturday-of- - // Mary votive this test isn't about (see tests/calendar/marian- - // saturday.test.ts); 2027 keeps both dates on plain midweek days. - const vigil = resolveDay('2027-06-23'); - const nativity = resolveDay('2027-06-24'); + const vigil = resolveDay('2035-06-23'); + const nativity = resolveDay('2035-06-24'); expect(vigil.winner).toEqual({ kind: 'sanctoral', id: 'vigil-of-st-john-the-baptist', diff --git a/tests/calendar/marian-saturday.test.ts b/tests/calendar/marian-saturday.test.ts index c844cf2..f55661b 100644 --- a/tests/calendar/marian-saturday.test.ts +++ b/tests/calendar/marian-saturday.test.ts @@ -2,11 +2,11 @@ import { describe, expect, it } from 'vitest'; import { resolveDay } from '../../src/calendar'; import { getDayLabel } from '../../src/calendar/day-label'; -// Direct instruction: on a free Saturday, "Our Lady's Saturday" mirrors -// privileged-feria-minor exactly — Semiduplex-or-higher still wins -// outright (Marian Saturday doesn't happen); below that (Simplex, and -// Vigil, since both are below semiduplex in the FeastClass scale), the -// saint loses and is commemorated instead. +// Direct instruction: on a free Saturday, "Our Lady's Saturday" is really +// only a Simplex-strength standing — a Simplex saint alone loses to it +// and is commemorated instead; anything Semiduplex-or-higher, or a Vigil +// (which keeps forcing its own primacy the same way it does any other +// day), still wins outright and Marian Saturday doesn't apply at all. describe('Marian Saturday (applyMarianSaturday)', () => { it('wins outright, with no commemoration, on a Saturday with nothing else at all going on', () => { const day = resolveDay('2026-10-24'); @@ -23,12 +23,21 @@ describe('Marian Saturday (applyMarianSaturday)', () => { ]); }); - it('wins over a Vigil too (below semiduplex, same as Simplex) -- can commemorate more than one loser at once', () => { - const day = resolveDay('2025-11-29'); // St. Saturninus (Simplex) + Vigil of St. Andrew - expect(day.winner).toEqual({ kind: 'temporal', id: 'marian-saturday' }); + it('loses to a Vigil, which keeps forcing its own primacy on a Saturday same as any other day -- Marian Saturday does not apply', () => { + // 2025-11-29: Vigil of St. Andrew collides with St. Saturninus + // (Simplex) via calendar/collision.ts (Vigil outranks Simplex there) + // before Marian Saturday even gets a look-in -- Vigil wins outright, + // Saturninus is commemorated as the collision's loser, not by Marian + // Saturday's own demotion rule. + const day = resolveDay('2025-11-29'); + expect(day.winner).toEqual({ + kind: 'sanctoral', + id: 'vigil-of-st-andrew', + name: 'Vigil of St. Andrew', + rank: 'vigil', + }); expect(day.commemorations).toEqual([ { kind: 'sanctoral', id: 'st-saturninus', name: 'St. Saturninus, Martyr', rank: 'simplex' }, - { kind: 'sanctoral', id: 'vigil-of-st-andrew', name: 'Vigil of St. Andrew', rank: 'vigil' }, ]); });