diff --git a/src/calendar/commemorations.ts b/src/calendar/commemorations.ts index af93cd2..5aa684d 100644 --- a/src/calendar/commemorations.ts +++ b/src/calendar/commemorations.ts @@ -84,6 +84,18 @@ export function decideOccurrence( // feast, however low-ranked, is kept in its place. return { winner: sanctoralWinner(sanctoral), commemorations: [] }; + case 'privileged-feria-minor': + // Advent's own ordinary ferias (verified: St. Bibiana, Simplex, + // merely commemorated under an Advent feria; St. Nicholas, + // Semiduplex, wins outright with the feria commemorated in return). + // Weaker than `privileged-feria`'s Duplex+ threshold — Semiduplex is + // already enough here — and unlike every other feria tier, nothing + // ever transfers: whichever side loses still gets a commemoration. + if (isAtLeast(sanctoral.rank, 'semiduplex')) { + return { winner: sanctoralWinner(sanctoral), commemorations: [{ kind: 'temporal', id: temporalId }] }; + } + return { winner: temporalWinner, commemorations: [sanctoralCommemoration(sanctoral)] }; + case 'privileged-feria': // The lesser of the two privileged-feria tiers (Ember days outside // Holy Week/the Easter Octave, etc.) — behaves exactly like an diff --git a/src/calendar/types.ts b/src/calendar/types.ts index 7a8523a..0da33dd 100644 --- a/src/calendar/types.ts +++ b/src/calendar/types.ts @@ -82,8 +82,19 @@ export type FeastClass = // categories.yml for exactly which days fall in which tier — some of that // split (e.g. whether Pentecost's own Ember days share Lent's lesser tier // or Easter's major one) is still a reconstructed guess, not verified. +// `privileged-feria-minor` is a third, weaker tier, added during the +// December sanctoral pull after finding Advent's own ordinary (non-Ember) +// ferias have real standing of their own in the live engine — a Simplex +// saint (e.g. St. Bibiana, Dec 2) is merely commemorated there, while a +// Semiduplex+ saint (e.g. St. Nicholas, Dec 6) wins outright with the +// feria itself commemorated in return — neither of which the original +// `ordinary-feria` fallback (any saint wins, nothing ever commemorated) +// could represent. Lent's own ordinary ferias are documented as sharing +// this same real-world property but are NOT switched to this tier yet — +// unverified this session, left as `ordinary-feria` pending a future check. export type TemporalCategory = | 'ordinary-feria' + | 'privileged-feria-minor' | 'privileged-feria' | 'privileged-feria-major' | 'ordinary-sunday' diff --git a/src/calendar/vespers.ts b/src/calendar/vespers.ts index e3cfca6..4f7fbd5 100644 --- a/src/calendar/vespers.ts +++ b/src/calendar/vespers.ts @@ -44,6 +44,7 @@ function hasFirstVespers(day: LiturgicalDay): boolean { function keepsOwnSecondVespers(day: LiturgicalDay): boolean { if ( day.weekday === 'sunday' || + day.temporalCategory === 'privileged-feria-minor' || day.temporalCategory === 'privileged-feria' || day.temporalCategory === 'privileged-feria-major' || isMajorFixedFeastOfTheLord(day.date) diff --git a/src/data/calendar/temporal-categories.yml b/src/data/calendar/temporal-categories.yml index 088d748..5945dc1 100644 --- a/src/data/calendar/temporal-categories.yml +++ b/src/data/calendar/temporal-categories.yml @@ -34,7 +34,11 @@ # Lent's and Pentecost's Ember days *are* modeled, since those are clean # Easter offsets. bySeason: - advent: { sunday: privileged-sunday, feria: ordinary-feria } + # Advent's ordinary ferias verified `privileged-feria-minor` during the + # December sanctoral pull (see calendar/types.ts's TemporalCategory doc + # comment) -- weaker than the Ember-day `privileged-feria` tier, but real + # standing nonetheless, unlike a plain `ordinary-feria`. + advent: { sunday: privileged-sunday, feria: privileged-feria-minor } # Christmastide's ferias are folded into the privileged-feria default for # the whole season as a simplification of "the Octave of Christmas (Dec # 25 - Jan 1) is privileged" — slightly overbroad into Jan 2-5, harmless. diff --git a/tests/calendar/commemorations.test.ts b/tests/calendar/commemorations.test.ts index f2ca6bb..bb4b4ec 100644 --- a/tests/calendar/commemorations.test.ts +++ b/tests/calendar/commemorations.test.ts @@ -41,6 +41,31 @@ describe('decideOccurrence — ordinary-feria', () => { }); }); +describe('decideOccurrence — privileged-feria-minor', () => { + it('semiduplex or higher wins outright, the feria is commemorated in return', () => { + // Verified: St. Nicholas, Semiduplex, outright wins against an + // ordinary (non-Ember) Advent Saturday in the real Monastic 1617 + // engine, with the feria itself commemorated back. + for (const rank of ['semiduplex', 'duplex', 'duplex-1-classis'] as const) { + const result = decideOccurrence('privileged-feria-minor', 'id', saint(rank)); + expect(result.winner).toEqual({ kind: 'sanctoral', id: 'x', name: 'St. Ereden', rank }); + expect(result.commemorations).toEqual([{ kind: 'temporal', id: 'id' }]); + expect(result.transfer).toBeUndefined(); + } + }); + + it('simplex or vigil stays and is commemorated, not transferred', () => { + // Verified: St. Bibiana, Simplex, merely commemorated under an + // ordinary Advent feria in the real Monastic 1617 engine. + for (const rank of ['simplex', 'vigil'] as const) { + const result = decideOccurrence('privileged-feria-minor', 'id', saint(rank)); + expect(result.winner).toEqual({ kind: 'temporal', id: 'id' }); + expect(result.commemorations).toEqual([{ kind: 'sanctoral', id: 'x', name: 'St. Ereden', rank }]); + expect(result.transfer).toBeUndefined(); + } + }); +}); + describe('decideOccurrence — privileged-feria', () => { it('behaves exactly like an ordinary Sunday — duplex or higher wins outright, the feria is commemorated in return', () => { // Verified: St. Gregory the Great, plain Duplex, outright wins against diff --git a/tests/calendar/november-sanctoral.test.ts b/tests/calendar/november-sanctoral.test.ts index 7dd687e..56dd86a 100644 --- a/tests/calendar/november-sanctoral.test.ts +++ b/tests/calendar/november-sanctoral.test.ts @@ -73,7 +73,10 @@ describe('November sanctoral pull (first pass)', () => { ]); }); - it('St. Andrew wins outright at Duplex II. classis with a real proper collect and antiphon, alone', () => { + it('St. Andrew wins outright at Duplex II. classis with a real proper collect and antiphon, commemorating the Advent feria itself in years his fixed date falls within Advent', () => { + // 2026-11-30 is also the Monday within Advent I's own week that year + // (privileged-feria-minor, see calendar/types.ts) -- the Advent feria + // is commemorated back, confirmed against the live reference engine. const day = resolveDay('2026-11-30'); expect(day.winner).toEqual({ kind: 'sanctoral', @@ -81,7 +84,7 @@ describe('November sanctoral pull (first pass)', () => { name: 'St. Andrew, Apostle', rank: 'duplex-2-classis', }); - expect(day.commemorations).toEqual([]); + expect(day.commemorations).toEqual([{ kind: 'temporal', id: 'advent-1' }]); expect(getDayCollect(day).status.en).toBe('verified'); }); });